Commits

Jonathan Eunice committed c99aad3

reorganized; tested under pypy; extended docs

  • Participants
  • Parent commits 1238ec9

Comments (0)

Files changed (9)

 explicitly represented or called by that name.``intensional`` helps Python
 programs represent intensional sets directly.
 
-**NB** Active construciton zone. 
+Usage
+=====
+
+``intensional`` defines several set ``IntensionalSet`` subclasses
+such as ``Any``, ``Every``, ``ButNot``,
+and ``EitherOr``. These correspond roughly to set operations union, intersection,
+difference, and symmetric difference (aka xor). Of these, ``Any`` is the most useful::
+
+    from intensional import *
+    
+    name = 'Stephen'
+    if name in Any('Steve', 'Steven', 'Stephen'):
+        print 'Good name!'
+
+So far, there's nothing here you couldn't do with standard Python ``set`` data types.
+So let's broaden out to more generic intensional sets::
+
+    if name in Test("x.startswith('S')"):
+        print "Your name starts with S!"
+
+``Test`` takes a lambda expression or string in its constructor. If it's a string, ``Test`` assumes
+the interesting variable name is ``x`` and compiles the string expression with an automatically provided
+``lambda x:`` prefix. This makes code a bit terser and cleaner. Now the sets start getting more
+interesting.::
+
+    starts_with_S =  Test("x.startswith('S')")
+    ends_with_n   =  Test("x.endswith('n')")
+    
+    if name in Every(starts_with_S, ends_with_n):
+        ...  # Stephen and Steven pass, but Steve does not
+        
+Of course, this could also be rendered as::
+    
+    if name in Test("x.startswith('S') and x.endswith('n')"):
+        ...  # Stephen and Steven pass, but Steve does not
+        
+Or even::
+
+    S_something_n = starts_with_S & ends_with_n
+    if name in S_something_n:
+        ...
+
+String Search
+=============
+
+``intensional`` defines sets for regular expression (``Re``) and glob (``Glob``) string matching.
+For example::
+
+    name = 'Stephen'
+    if name in Re(r'\b(s\w*)\b', Re.I):
+        print 'Good name, {}'.format(Re._[1])
+
+Note that this enables a form of *en passant* assignment
+that shortens regular expression conditionals by at least one line
+compared to the standard ``re`` module.
+
+For simple matching, the ``Glob`` class (which plays by the rules
+of Unix glob expressions) may be simpler:
+
+    if name in Glob('S*'):
+        ...
+    
+Type Membership
+===============
+
+::
+
+    if x in Instances(int):
+        ...
+
+is identical to::
+
+    if isinstance(x, int):
+       ...
+       
+An alias ``IsInstance`` exists for ``Instances`` for cases where the singular construction is more linguistically natural.
+A second alias ``Type`` is also available.
+
+Set Operations
+==============
+
+``intensional`` supports some, but not all, of Python's classic ``set`` operations.
+There are two primary rules:
+
+ *  ``IntensionalSet`` attempts to supports all of the ``collections.Set`` methods like
+    ``union()`` and ``intersection``. But they are immutable, so they do not support
+    self-mutating operations like ``add()``, ``pop()``, and ``|=`` defined by
+    ``collections.MutableSet``.
+    
+ *  Because they are defined by rules rather than explicit lists of members, it is
+    not (in general) possible to determine the cardinality (i.e. ``len()``) of an ``IntensionalSet``,
+    nor to iterate through all of its members, nor to test equality. ``IntensionalSet`` objects are
+    used primarily for determining membership.
+
+Because of an implementation detail, ``IntensionalSet`` classes are parallel to,
+but are not true subclasses of, ``collections.Set``.
+
+Extensions
+==========
+
+It's easy to define new ``IntensionalSet`` subclasses that define other kinds
+of logical tests in generalized, linguistically "clean" ways that make code
+more readable. As an example, the ``Instances`` intensional set is defined like this::
+
+    class Instances(with_metaclass(MementoMetaclass, IntensionalSet)):
+        """
+        An object is in an IsInstance if it is an instance of the given types.
+        """
+        def __init__(self, *args):
+            self.types = tuple(args)
+            
+        def __contains__(self, item):
+            return isinstance(item, self.types)
+
+``__init__()`` simply remembers what arguments the set is constructed with,
+while ``__contains__()`` implements the test, answering: Does the given item belong in a set
+constructed with these arguments?
+
+The only complexity here is the ``with_metaclass(MementoMetaclass, IntensionalSet)`` phrase,
+which is simply a compatibility mechanism to be able to define a class in either
+Python 2 or Python 3 with a given metaclass.
+
+``MementoMetaclass`` is used so that
+once constructed, a set object is fetched from cache rather than
+redundantly reconstructed if any subsequent mentions are made. This is a useful performance
+tweak. For regular expressions, for example, it allows the ``Re.__init__()`` set constructor
+to compile the regular expression just once, even if a program contains many mentions of
+``Re(<some regular exprssion>)``. Even
+higher-performance is to assign constructed sets to a name/variable and refer to them
+via that name. This::
+
+    integers = Instances(int)
+    
+    if x in integers:
+        ...
+
+requires less work than::
+
+    if x in Instances(int):
+        ...
+
+and is preferred if the test is to be executed frequently. But this pre-naming is just a tweak, and
+not a requirement.
+
+Notes
+=====
+
+ * Commenced automated multi-version testing with
+   `pytest <http://pypi.python.org/pypi/pytest>`_
+   and `tox <http://pypi.python.org/pypi/tox>`_.
+   
+ * Now
+   successfully packaged for, and tested against, all late-model versions
+   of Python: 2.6, 2.7, 3.2, and 3.3
+   plus one (2.5) that isn't so very recent,
+   and one (PyPy 1.9, based on Python 2.7.2) that is differently implemented.
+   
+ * ``intensional`` is just one facet of a larger project to rethink how items
+   are tested for membership and/or chosen from collections. Stay tuned!
+ 
+ * The author, `Jonathan Eunice <mailto:jonathan.eunice@gmail.com>`_ or
+   `@jeunice on Twitter <http://twitter.com/jeunice>`_
+   welcomes your comments and suggestions.
 
 Installation
 ============
 ::
 
     pip install intensional
+
+To ``easy_install`` under a specific Python version (3.3 in this example)::
+
+    python3.3 -m easy_install intensional
     
-(You may need to prefix this with "sudo " to authorize installation.)
+(You may need to prefix these with "sudo " to authorize installation. If they're
+already installed, the ``--upgrade`` flag will be helpful; add it right before the
+package name.)

__init__.py

-from intensional import *

intensional.py

-
-from mementos import MementoMetaclass   # to memoize IntensionalSet
-import re                               # for Re
-import fnmatch                          # for Glob
-import sys                              # for en passant operation in Re
-# import six
-import copy
-
-if sys.version_info[0] > 2:
-    unicode = str
-
-class IntensionalSet(object):
-    """
-    An intensional set (actually, an intensionally defined set) is a set
-    defined by a rule rather than an explicit listing of members
-    (which would be an extensional set).
-    
-    Like other Python sets, IntensionalSet objects can include explicit
-    items--but it also admits the possibility of rule-based membership.
-
-    """
-
-    __metaclass__ = MementoMetaclass
-    
-    def union(self, other):
-        return Any(self, other)
-    
-    def intersection(self, other):
-        return Every(self, other)
-    
-    def difference(self, other):
-        return ButNot(self, other)
-    
-    def symmetric_difference(self, other):
-        return EitherOr(self, other)
-    
-    def __or__(self, other):
-        # |, equivalent to union
-        return Any(self, other)
-    
-    def __and__(self, other):
-        # &, equivalent to intersection
-        return Every(self, other)
-    
-    def __sub__(self, other):
-        # -, equivalent to difference
-        return ButNot(self, other)
-    
-    def __xor__(self, other):
-        # ^, equivalent to symmetric_difference
-        return EitherOr(self, other)
-    
-    def __contains__(self, item):
-        raise TypeError('Should have been implemented by subclass')
-    
-    def __len__(self):
-        raise TypeError('Impossible to compute the cardinality of an intensional set.')
-    
-    def issubset(self, other):
-        raise TypeError('Impossible to test subset relation between intensional sets.')
-    
-    def __le__(self, other):
-        # <=, equivalent to issubset
-        raise TypeError('Impossible to test subset relation between intensional sets.')
-
-    def issuperset(self, other):
-        raise TypeError('Impossible to test superset relation between intensional sets.')
-    
-    def __le__(self, other):
-        # >=, equivalent to issuperset
-        raise TypeError('Impossible to test superset relation between intensional sets.')
-    
-    def copy(self):
-        return copy.copy(self)
-
-    def pop(self):
-        raise TypeError('Impractical to pop() an item from an intensional set.')
-        # and impossible in the general case
-    
-    ### remaining to be implemented
-    
-    # Many of these are self-modifying methods, which are harder to implement in the
-    # scheme we currenly have. If we had a top-level Set object with inclusions and
-    # exclusions components, could probably jigger these up. 
-    
-    def update(self, others):
-        raise NotImplementedError()
-    
-    union_update = update # backwards compatibility
-    
-    def __ior__(self, other):
-        # |=, similar to update but takes only one item, not an iterable
-        raise NotImplementedError()
-        
-    def intersection_update(self, others):
-        raise NotImplementedError()
-
-    def __iand__(self, other):
-        # &=, similar to intersection_update but takes only one item, not an iterable
-        raise NotImplementedError()
-    
-    def difference_update(self, others):
-        raise NotImplementedError()
-
-    def __isub__(self, other):
-        # -=, similar to difference_update but takes only one item, not an iterable
-        raise NotImplementedError()
-    
-    def symmetric_difference_update(self, others):
-        raise NotImplementedError()
-
-    def __ixor__(self, other):
-        # ^=, similar to symmetric_difference_update but takes only one item, not an iterable
-        raise NotImplementedError()
-    
-    def add(self, x):
-        raise NotImplementedError()
-    
-    def remove(self, x):
-        raise NotImplementedError()
-    
-    def discard(self, x):
-        raise NotImplementedError()
-    
-    def clear(self):
-        raise NotImplementedError()
-
-    
-    
-# Any = Union, Every = Intersection, ButNot = Difference, EitherOr = Xor / Symmetric Difference
-# might want to complete more of the set functions like symmetric difference, discard, etc
-
-# should this be reorganized into multiple files?
-
-class ReMatch(object):
-    """
-    An easier-to-use proxy for regular expression match objects. Ideally this would be
-    a subclass of the re module's match object, but their type ``_sre.SRE_Match``
-    `appears to be unsubclassable
-    <http://stackoverflow.com/questions/4835352/subclassing-matchobject-in-python>`_.
-    Thus, ReMatch is a proxy exposes the match object's numeric (positional) and
-    named groups through indices and attributes. If a named group has the same
-    name as a match object method or property, it takes precedence. Either
-    change the name of the match group or access the underlying property thus:
-    ``x._match.property``
-    """
-     
-    def __init__(self, match):
-        self._match = match
-        self._groupdict = match.groupdict()
-        
-    def __getattr__(self, name):
-        if name in self.__dict__:
-            return self.__dict__[name]
-        if name in self._groupdict:
-            return self._groupdict[name]
-        try:
-            return getattr(self._match, name)
-        except AttributeError:
-            return AttributeError("no such attribute '{}'".format(name))
-        
-    def __getitem__(self, index):
-        return self._match.group(index)
-
-#def show_frames():
-#    f = inspect.currentframe().f_back # the caller
-#    while f:
-#        six.print_(f, f.f_lineno)
-#        six.print_("   f_locals:", ' '.join(f.f_locals.keys()))
-#        f = f.f_back
-
-EN_PASSANT_NAME = '_'  # any reason to make this settable by the user?
-
-class Re(IntensionalSet):
-    
-    # convenience copy of re flag constants
-    
-    DEBUG      = re.DEBUG
-    I          = re.I
-    IGNORECASE = re.IGNORECASE
-    L          = re.L
-    LOCALE     = re.LOCALE
-    M          = re.M
-    MULTILINE  = re.MULTILINE
-    S          = re.S
-    DOTALL     = re.DOTALL
-    U          = re.U
-    UNICODE    = re.UNICODE
-    X          = re.X
-    VERBOSE    = re.VERBOSE
-    
-    _ = None
-    
-    def __init__(self, pattern, flags=0):
-        self.pattern = pattern
-        self.flags   = flags
-        self.re = re.compile(pattern, flags)
-        self.groups     = self.re.groups
-        self.groupindex = self.re.groupindex
-        
-    def _regroup(self, m):
-        """
-        Given an _sre.SRE_Match object, create and return a corresponding
-        ReMatch object. Also, set the en passant variable to it.
-        """
-
-        result = ReMatch(m) if m else m
-        Re._ = result
-        # _regroup() -> calling method -> MementosMetaclass.__call__ ->
-        # Re.__init__ -> caller (4 levels back)
-        # sys._getframe(4).f_locals[EN_PASSANT_NAME] = result
-        
-        # Not clear we can get the en passant idea to work reliably
-        # until we can, backing that idea out
-
-        return result
-
-    def __contains__(self, item):
-        if not isinstance(item, (str, unicode)):
-             item = str(item)
-        return self._regroup(self.re.search(item))
-    
-    ### methods that return ReMatch objects
-    
-    def search(self, *args, **kwargs):
-        return self._regroup(self.re.search(*args, **kwargs))
-
-    def match(self, *args, **kwargs):
-        return self._regroup(self.re.match(*args, **kwargs))
-    
-    def finditer(self, *args, **kwargs):
-        return self.re.finditer(*args, **kwargs)
- 
-    ### methods that don't need ReMatch objects
-      
-    def findall(self, *args, **kwargs):
-        return self.re.findall(*args, **kwargs)
-    
-    def split(self, *args, **kwargs):
-        return self.re.split(*args, **kwargs)
-    
-    def sub(self, *args, **kwargs):
-        return self.re.sub(*args, **kwargs)
-    
-    def subn(self, *args, **kwargs):
-        return self.re.subn(*args, **kwargs)
-    
-    def escape(self, *args, **kwargs):
-        return self.re.escape(*args, **kwargs)
-
-class Glob(IntensionalSet):
-    """
-    An item matches a Glob via Unix filesystem glob semantics.
-    
-    E.g. 'alpha' matches 'a*' and 'a????' but not 'b*'
-    """
-        
-    def __init__(self, pattern):
-        self.pattern = pattern
-        
-    def __contains__(self, item):
-        return fnmatch.fnmatch(item, self.pattern)
-
-class IsInstance(IntensionalSet):
-    """
-    An object is in an IsInstance if it is an instance of the given types.
-    """
-    def __init__(self, *args):
-        self.types = tuple(args)
-        
-    def __contains__(self, item):
-        return isinstance(item, self.types)
-    
-class Any(IntensionalSet):
-    """
-    An item is in an Any if it is or is in any member of the set.
-    """
-    def __init__(self, *args):
-        self.items = set(args)
-        
-    def __contains__(self, item):
-        if item in self.items:
-            return True
-        
-        for i in self.items:
-            if hasattr(i, '__contains__'):
-                if item in i:
-                    return True
-            else:
-                if item == i:
-                    return True
-        return False
-    
-class Every(IntensionalSet):
-    """
-    An item is in an Every if it is or is in every member of the set.
-    """
-    def __init__(self, *args):
-        self.items = set(args)
-
-    def __contains__(self, item):
-        for i in self.items:
-            if hasattr(i, '__contains__'):
-                if item not in i:
-                    return False
-            else:
-                if item != i:
-                    return False
-        return True
-
-class ButNot(IntensionalSet):
-    """
-    An item is in a ButNot if it's in the primary set and not the exclusion.
-    """
-    def __init__(self, items, exclusion):
-        self.items = items
-        self.exclusion = exclusion
-
-    def __contains__(self, item):        # why the == self.items? 
-        if item == self.items or item in self.items:
-            if item != self.exclusion and item not in self.exclusion:
-                return True
-        return False
-    
-class EitherOr(IntensionalSet):
-    """
-    An item is in an EitherOr if it's in subseta or subset b, but not both.
-    """
-    def __init__(self, a, b):
-        self.a = a
-        self.b = b
-
-    def __contains__(self, item):
-        if item in self.a:
-            return not item in self.b
-        elif item in self.b:
-            return not item in self.a
-        else:
-            return False
-        
-class Test(IntensionalSet):
-    """
-    Test is a generic wrapper around lambda expressions.
-    Provides special support for compact, neat expressions by not
-    auto-adding a 'lambda x:' prefix if test provided as a string.
-    """
-    def __init__(self, expr, *args, **kwargs):
-        IntensionalSet.__init__(self)
-        self.args = args
-        self.kwargs = kwargs
-        self.expr = expr
-        if isinstance(expr, basestring):
-            if not expr.startswith('lambda'):
-                expr = 'lambda x: ' + expr
-            self.func = eval(expr)
-        elif hasattr(expr, '__call__'):
-            self.func = expr
-        else:
-            raise ValueError('expr needs to be string or callable')
-        
-    def __contains__(self, item):
-        try:
-            return self.func(item, *self.args, **self.kwargs)
-        except StandardError:
-            return False
-        
-        # NB failure to run test => fails test
-        # might silently hide syntax errors and such
-        # do we want a mode or mechanism to make such errors into Warnings?

intensional/__init__.py

+try:
+    from core import *
+except ImportError:
+    from intensional.core import *

intensional/core.py

+
+from mementos import MementoMetaclass   # to memoize IntensionalSet
+import re                               # for Re
+import fnmatch                          # for Glob
+# import six
+import sys, copy
+import collections
+
+if sys.version_info[0] > 2:
+    unicode = str
+    basestring = str
+
+def with_metaclass(meta, base=object):
+    """Create a base class with a metaclass."""
+    return meta("NewBase", (base,), {})
+
+class IntensionalSet(object):
+    """
+    An intensional set (actually, an intensionally defined set) is a set
+    defined by a rule rather than an explicit listing of members
+    (which would be an extensional set).
+    
+    Like other Python sets, IntensionalSet objects can include explicit
+    items--but it also admits the possibility of rule-based membership.
+    """
+    
+    def union(self, other):
+        return Any(self, other)
+    
+    def intersection(self, other):
+        return Every(self, other)
+    
+    def difference(self, other):
+        return ButNot(self, other)
+    
+    def symmetric_difference(self, other):
+        return EitherOr(self, other)
+    
+    def __or__(self, other):
+        # |, equivalent to union
+        return Any(self, other)
+    
+    def __and__(self, other):
+        # &, equivalent to intersection
+        return Every(self, other)
+    
+    def __sub__(self, other):
+        # -, equivalent to difference
+        return ButNot(self, other)
+    
+    def __xor__(self, other):
+        # ^, equivalent to symmetric_difference
+        return EitherOr(self, other)
+    
+    def __contains__(self, item):
+        raise TypeError('Should have been implemented by subclass')
+    
+    def __len__(self):
+        raise TypeError('Impossible to compute the cardinality of an intensional set.')
+    
+    def issubset(self, other):
+        raise TypeError('Impossible to test subset relation between intensional sets.')
+    
+    def __le__(self, other):
+        # <=, equivalent to issubset
+        raise TypeError('Impossible to test subset relation between intensional sets.')
+
+    def issuperset(self, other):
+        raise TypeError('Impossible to test superset relation between intensional sets.')
+    
+    def __le__(self, other):
+        # >=, equivalent to issuperset
+        raise TypeError('Impossible to test superset relation between intensional sets.')
+    
+    def copy(self):
+        return copy.copy(self)
+
+    def pop(self):
+        raise TypeError('Impractical to pop() an item from an intensional set.')
+        # and impossible in the general case
+    
+    ### remaining to be implemented
+    
+    # Many of these are self-modifying methods, which are harder to implement in the
+    # scheme we currenly have. If we had a top-level Set object with inclusions and
+    # exclusions components, could probably jigger these up. 
+    
+    def update(self, others):
+        raise NotImplementedError()
+    
+    union_update = update # backwards compatibility
+    
+    def __ior__(self, other):
+        # |=, similar to update but takes only one item, not an iterable
+        raise NotImplementedError()
+        
+    def intersection_update(self, others):
+        raise NotImplementedError()
+
+    def __iand__(self, other):
+        # &=, similar to intersection_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def difference_update(self, others):
+        raise NotImplementedError()
+
+    def __isub__(self, other):
+        # -=, similar to difference_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def symmetric_difference_update(self, others):
+        raise NotImplementedError()
+
+    def __ixor__(self, other):
+        # ^=, similar to symmetric_difference_update but takes only one item, not an iterable
+        raise NotImplementedError()
+    
+    def add(self, x):
+        raise NotImplementedError()
+    
+    def remove(self, x):
+        raise NotImplementedError()
+    
+    def discard(self, x):
+        raise NotImplementedError()
+    
+    def clear(self):
+        raise NotImplementedError()
+    
+# Any = Union, Every = Intersection, ButNot = Difference, EitherOr = Xor / Symmetric Difference
+# might want to complete more of the set functions like symmetric difference, discard, etc
+   
+class ReMatch(object):
+    """
+    An easier-to-use proxy for regular expression match objects. Ideally this would be
+    a subclass of the re module's match object, but their type ``_sre.SRE_Match``
+    `appears to be unsubclassable
+    <http://stackoverflow.com/questions/4835352/subclassing-matchobject-in-python>`_.
+    Thus, ReMatch is a proxy exposes the match object's numeric (positional) and
+    named groups through indices and attributes. If a named group has the same
+    name as a match object method or property, it takes precedence. Either
+    change the name of the match group or access the underlying property thus:
+    ``x._match.property``
+    """
+     
+    def __init__(self, match):
+        self._match = match
+        self._groupdict = match.groupdict()
+        
+    def __getattr__(self, name):
+        if name in self.__dict__:
+            return self.__dict__[name]
+        if name in self._groupdict:
+            return self._groupdict[name]
+        try:
+            return getattr(self._match, name)
+        except AttributeError:
+            return AttributeError("no such attribute '{}'".format(name))
+        
+    def __getitem__(self, index):
+        return self._match.group(index)
+
+class Re(with_metaclass(MementoMetaclass, IntensionalSet)):
+    
+    # convenience copy of re flag constants
+    
+    DEBUG      = re.DEBUG
+    I          = re.I
+    IGNORECASE = re.IGNORECASE
+    L          = re.L
+    LOCALE     = re.LOCALE
+    M          = re.M
+    MULTILINE  = re.MULTILINE
+    S          = re.S
+    DOTALL     = re.DOTALL
+    U          = re.U
+    UNICODE    = re.UNICODE
+    X          = re.X
+    VERBOSE    = re.VERBOSE
+    
+    _ = None
+    
+    def __init__(self, pattern, flags=0):
+        self.pattern = pattern
+        self.flags   = flags
+        self.re = re.compile(pattern, flags)
+        self.groups     = self.re.groups
+        self.groupindex = self.re.groupindex
+        
+    def _regroup(self, m):
+        """
+        Given an _sre.SRE_Match object, create and return a corresponding
+        ReMatch object. Also, set the en passant variable to it.
+        """
+
+        result = ReMatch(m) if m else m
+        Re._ = result
+        return result
+
+    def __contains__(self, item):
+        if not isinstance(item, (str, unicode)):
+             item = str(item)
+        return self._regroup(self.re.search(item))
+    
+    ### methods that return ReMatch objects
+    
+    def search(self, *args, **kwargs):
+        return self._regroup(self.re.search(*args, **kwargs))
+
+    def match(self, *args, **kwargs):
+        return self._regroup(self.re.match(*args, **kwargs))
+    
+    def finditer(self, *args, **kwargs):
+        return self.re.finditer(*args, **kwargs)
+ 
+    ### methods that don't need ReMatch objects
+      
+    def findall(self, *args, **kwargs):
+        return self.re.findall(*args, **kwargs)
+    
+    def split(self, *args, **kwargs):
+        return self.re.split(*args, **kwargs)
+    
+    def sub(self, *args, **kwargs):
+        return self.re.sub(*args, **kwargs)
+    
+    def subn(self, *args, **kwargs):
+        return self.re.subn(*args, **kwargs)
+    
+    def escape(self, *args, **kwargs):
+        return self.re.escape(*args, **kwargs)
+
+class Glob(with_metaclass(MementoMetaclass, IntensionalSet)):
+    """
+    An item matches a Glob via Unix filesystem glob semantics.
+    
+    E.g. 'alpha' matches 'a*' and 'a????' but not 'b*'
+    """
+        
+    def __init__(self, pattern):
+        self.pattern = pattern
+        
+    def __contains__(self, item):
+        return fnmatch.fnmatch(item, self.pattern)
+
+class Instances(with_metaclass(MementoMetaclass, IntensionalSet)):
+    """
+    An object is in an IsInstance if it is an instance of the given types.
+    """
+    def __init__(self, *args):
+        self.types = tuple(args)
+        
+    def __contains__(self, item):
+        return isinstance(item, self.types)
+    
+Type = Instances
+IsInstance = Instances
+    
+def boxed(item):
+    """
+    Return item in a container if it is a scalar, else the item itself.
+    Aka box it up, unless it's already boxed.
+    """
+    return item if isinstance(item, (list, set)) else [ item ]
+
+class Set(IntensionalSet):
+    """
+    Set that has both inclusions and exclusions. An item is in a Aset if it is, or
+    is in, any of the inclusions--as long as it is not equal to or included in
+    any of the exclusions. A convenient hybrid of the Any and ButNot set types.
+    Some set operations like union can be performed without requiring returning
+    a different subclass of IntensionalSet. Provides more opporunity also for
+    in-place mutations.
+    """
+    def __init__(self, include, exclude=[]):
+        self.include = boxed(include)
+        self.exclude = boxed(exclude)
+    
+    def _included(self, item):
+        for i in self.include:
+            if item == i:
+                return True
+            elif hasattr(i, '__contains__'):
+                if item in i:
+                    return True
+        return False
+    
+    def _excluded(self, item):
+        for i in self.exclude:
+            if item == i:
+                return True
+            elif hasattr(i, '__contains__'):
+                if item in i:
+                    return True
+        return False
+        
+    def __contains__(self, item):    
+        return self._included(item) and not self._excluded(item)
+    
+    def union(self, other):
+        clone = self.copy()
+        if other not in clone.include:
+            clone.include.append(other)
+        return clone
+        
+    def difference(self, other):
+        clone = self.copy()
+        if other not in clone.exclude:
+            clone.exclude.append(other)
+        return clone
+    
+class Any(IntensionalSet):
+    """
+    An item is in an Any if it is or is in any member of the set.
+    """
+    def __init__(self, *args):
+        self.items = set(args)
+        
+    def __contains__(self, item):
+        if item in self.items:
+            return True
+        
+        for i in self.items:
+            if hasattr(i, '__contains__'):
+                if item in i:
+                    return True
+            else:
+                if item == i:
+                    return True
+        return False
+    
+class Every(IntensionalSet):
+    """
+    An item is in an Every if it is or is in every member of the set.
+    """
+    def __init__(self, *args):
+        self.items = set(args)
+
+    def __contains__(self, item):
+        for i in self.items:
+            if hasattr(i, '__contains__'):
+                if item not in i:
+                    return False
+            else:
+                if item != i:
+                    return False
+        return True
+
+class ButNot(IntensionalSet):
+    """
+    An item is in a ButNot if it's in the primary set and not the exclusion.
+    """
+    def __init__(self, items, exclusion):
+        self.items = items
+        self.exclusion = exclusion
+
+    def __contains__(self, item):        # why the == self.items? 
+        if item == self.items or item in self.items:
+            if item != self.exclusion and item not in self.exclusion:
+                return True
+        return False
+    
+class EitherOr(IntensionalSet):
+    """
+    An item is in an EitherOr if it's in subseta or subset b, but not both.
+    """
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __contains__(self, item):
+        if item in self.a:
+            return not item in self.b
+        elif item in self.b:
+            return not item in self.a
+        else:
+            return False
+        
+class Test(IntensionalSet):
+    """
+    Test is a generic wrapper around lambda expressions.
+    Provides special support for compact, neat expressions by not
+    auto-adding a 'lambda x:' prefix if test provided as a string.
+    """
+    def __init__(self, expr, *args, **kwargs):
+        IntensionalSet.__init__(self)
+        self.args = args
+        self.kwargs = kwargs
+        self.expr = expr
+        if isinstance(expr, basestring): 
+            if not expr.startswith('lambda'):
+                expr = 'lambda x: ' + expr
+            self.func = eval(expr)
+        elif hasattr(expr, '__call__'):
+            self.func = expr
+        else:
+            raise ValueError('expr needs to be string or callable')
+        
+    def __contains__(self, item):
+        try:
+            return self.func(item, *self.args, **self.kwargs)
+        except Exception:
+            return False
+        
+    # NB failure to run test => fails test
+    # might silently hide syntax errors and such
+    # do we want a mode or mechanism to make such errors into Warnings?
+
+  * A nicer way to use regular expressions
+  * A ``switch`` statement (actually, function) for Python
+
+
+Usage
+=====
+
+Instead of::
+
+    import re
+    
+    match = re.search(pattern, some_string)
+    if match:
+        print match.group(1)
+
+You can do an *en passant* test::
+
+    from intensional import Re
+    
+    if some_string in Re(pattern):
+        print _[1]
+        
+There are two things to note here: First,
+this turns the sense of the matching around, asking "is a given string *in*
+the set of items this pattern describes?" The ``Re`` pattern is an intensionally
+defined set (namely "all strings matching the pattern"). This order often makes
+excellent sense whey you have a clear intent for the test. For example, "is the
+given string within the set of *all legitimate commands*?"
+
+Second, the ``in`` test had the side effect of setting the underscore
+name ``_`` to the result. Python doesn't support *en passant* assignment, so
+you can't both test and collect results in the same motion, even though that's
+sometimes exactly appropriate. ``Re`` use introspection to get around this
+difficulty and provide neat results.
+
+If you prefer the more traditional ``re`` calls, you can still use them with the
+convenient *en passant* style.::
+
+    if Re(pattern).search(some_string):
+        print _[1]
+        
+``Re`` works even better with named pattern components, such as::
+
+    person = 'John Smith 48'
+    if person in Re(r'(?P<name>[\w\s]*)\s+(?P<age>\d+)'):
+        print _.name, "is", _.age, "years old"
+    else:
+        print "don't understand '{}'".format(person)
+        
+        
+It's possible also to loop over the results, as in::
+
+    for found in Re(r'(pattern\w*)').findall('pattern is as pattern does'):
+        print found
+        
+``Re`` objects are `memoized <http://en.wikipedia.org/wiki/Memoization>`_ for efficiency, so they
+are only compiled once, regardless of how many times they're mentioned in the program.
+
+
+    
+Notes
+=====
+
+It's entirely possible to implement an intensional set like idea with
+other Python mechanisms, such as ``lambda`` expressions, test functions,
+and list comprehensions. Many (most?) Python programs already *de facto*
+use this implicit intensional set approach. Making intensional sets
+explicit, however, has the benefit of making what's being done more
+clear. It provides a mechanism for cleaning up several sort of ucky
+parts of Python:
+
+ *  Messy, guts-exposed regular expression tests
+ *  Overly verbose lambda expressions
+ *  Long, non-DRY ``if``/``elif``/``else`` constructions (with ``switch``)
+ *  A mess of mechanisms for collection filtering
 
 setup(
     name='intensional',
-    version=verno("0.105"),
+    version=verno("0.150"),
     author='Jonathan Eunice',
     author_email='jonathan.eunice@gmail.com',
     description='Intensional sets in Python',
     long_description=open('README.rst').read(),
     url='https://bitbucket.org/jeunice/intensional',
-    py_modules=['intensional'],
-    install_requires=['stuf','mementos'],
+    packages=['intensional'],
+    install_requires=['stuf>=0.9.10','mementos>=0.45'],
     classifiers=linelist("""
         Development Status :: 3 - Alpha
         Operating System :: OS Independent
         License :: OSI Approved :: BSD License
         Intended Audience :: Developers
         Programming Language :: Python
+        Programming Language :: Python :: 2.5
         Programming Language :: Python :: 2.6
         Programming Language :: Python :: 2.7
+        Programming Language :: Python :: 3.2
+        Programming Language :: Python :: 3.3
+        Programming Language :: Python :: Implementation :: CPython
+        Programming Language :: Python :: Implementation :: PyPy
         Topic :: Software Development :: Libraries :: Python Modules
     """)
 )
         assert Re._.age  == Re._._match.group('age')
     else:
         raise ValueError('yes it is!!!')
+
+def in_out(s, includes, excludes):
+    """
+    Testing shortcut. Assert s includes everything in includes and
+    excludes everything in excludes. Encourages compact in/out testing
+    and therefore better coverage.
+    """
+    for item in includes:
+        assert item in s
+    for item in excludes:
+        assert item not in s
+    
+def test_Set():
+    s = Set([1,2,44], exclude=44)
+    in_out(s, [1, 2],
+              [44, 3, 100, "string"])
+    
+    t = s.union([77, "other"])
+    in_out(t, [1, 2, 77, "other"],
+              [44, 3, 100, "string"])
+    
+    u = t.difference(Any(2, 99))
+    in_out(t, [1, 77, "other"],
+              [44, 2, 99, 3, 100, "string"])
     
 def test_Any():
     ext = Any(1,2,3)
     assert 'yo' in IsInstance(str)
     assert 333 not in IsInstance(str)
     assert {} in IsInstance(dict)
+    
+def test_Instances():
+    assert 1 in Instances(int)
+    assert 'yo' in Instances(str)
+    assert 333 not in Instances(str)
+    assert {} in Instances(dict)
+    
+def test_Type():
+    assert 1 in Type(int)
+    assert 'yo' in Type(str)
+    assert 333 not in Type(str)
+    assert {} in Type(dict)
+    
+def test_examples():
+        
+    in_out(Any('Steve', 'Steven', 'Stephen'),
+           ['Steve', 'Steven', 'Stephen'],
+           ['Joe', 'Frank', 'Bill', 'Sally', 'Stan'])
+    
+    in_out(Test("x.startswith('S')"),
+           ['Steve', 'Steven', 'Stephen', 'Sally', 'Stan'],
+           ['Joe', 'Frank', 'Bill'])
+
+
+    starts_with_S =  Test("x.startswith('S')")
+    ends_with_n   =  Test("x.endswith('n')")
+    
+    in_out(Every(starts_with_S, ends_with_n),
+           ['Steven', 'Stephen', 'Stan'],
+           ['Joe', 'Frank', 'Bill', 'Steve', 'Sally'])
+    
+    in_out(Test("x.startswith('S') and x.endswith('n',)"),
+           ['Steven', 'Stephen', 'Stan'],
+           ['Joe', 'Frank', 'Bill', 'Steve', 'Sally'])
+
+    S_something_n = starts_with_S & ends_with_n
+    
+    in_out(S_something_n,
+           ['Steven', 'Stephen', 'Stan'],
+           ['Joe', 'Frank', 'Bill', 'Steve', 'Sally'])
+
+    integers = Instances(int)
+    in_out(integers,
+           [1,3,4,5,-33, 0, 137423],
+           [0.4, 4+9j, "string", object, object(), KeyError, KeyError() ])
+    
+    in_out(Instances(int),
+           [1,3,4,5,-33, 0, 137423],
+           [0.4, 4+9j, "string", object, object(), KeyError, KeyError() ])
+    
+    assert integers is Instances(int)
 [tox]
-envlist = py26, py27
+envlist = py25, py26, py27, py32, py33, pypy
 
 
 [testenv]