[Python-checkins] r54976 - sandbox/trunk/abc/abc.py sandbox/trunk/abc/test_abc.py

guido.van.rossum python-checkins at python.org
Thu Apr 26 01:37:42 CEST 2007


Author: guido.van.rossum
Date: Thu Apr 26 01:37:38 2007
New Revision: 54976

Modified:
   sandbox/trunk/abc/abc.py
   sandbox/trunk/abc/test_abc.py
Log:
Make it match PEP 3119 better.


Modified: sandbox/trunk/abc/abc.py
==============================================================================
--- sandbox/trunk/abc/abc.py	(original)
+++ sandbox/trunk/abc/abc.py	Thu Apr 26 01:37:38 2007
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3.0
 
-"""Abstract Base Classes experiment.
+"""Abstract Base Classes experiment.  See PEP 3119.
 
 Note: this depends on the brand new Py3k feature that object.__ne__()
 is implemented by calling __eq__() and reversing the outcome (unless
@@ -17,7 +17,12 @@
 import sys
 
 
-### ABC SUPPORT ###
+### ABC SUPPORT FRAMEWORK ###
+
+
+# Note: @abstractmethod will become a built-in; Abstract and
+# AbstractClass will disappear (the functionality will be subsumed in
+# object and type, respectively).
 
 
 def abstractmethod(funcobj):
@@ -44,6 +49,9 @@
             @abstractmethod
             def my_abstract_class_method(self, ...):
                 ...
+
+    XXX This example doesn't currently work, since classmethod doesn't
+    pass through function attributes!  I think it should though.
     """
     funcobj.__isabstractmethod__ = True
     return funcobj
@@ -64,64 +72,65 @@
                 if getattr(value, "__isabstractmethod__", False):
                     abstracts.add(name)
         cls.__abstractmethods__ = abstracts
-        if abstracts:
-            _disable_construction(cls)
         return cls
 
 
-def _disable_construction(cls):
-    """Helper to ensure that a class cannot be instantiated.
+class Abstract(metaclass=AbstractClass):
+
+    """Base class to support the abstractmethod decorator.
 
-    This is done by planting a __new__ method that raises an exception
-    if its first argument is the cls argument to this function.
+    This implicitly sets the metaclass to AbstractClass.
     """
-    # XXX This still may be too much overhead; imagine a concrete class
-    # deriving from a stack of ABCs, it will bounce off each ABC's
-    # __new__ method
-    # XXX Should we store the shadowed function on the class?
-    # XXX Is it a good idea to name the new function __new__?
-    dct = cls.__dict__
-    shadowed_new = dct.get("__new__")
-    if not shadowed_new:
-        @staticmethod
-        def __new__(c, *a, **k):
-            if c is cls:
-                raise AbstractInstantiationError(cls.__abstractmethods__)
-            return super(cls, c).__new__(c, *a, **k)
-    else:
-        @staticmethod
-        def __new__(c, *a, **k):
-            if c is cls:
-                raise AbstractInstantiationError(cls.__abstractmethods__)
-            return shadowed_new(c, *a, **k)
-    cls.__new__ = __new__
 
+    def __new__(cls, *args, **kwds):
+        am = cls.__dict__.get("__abstractmethods__")
+        if am:
+            raise TypeError("can't instantiate abstract class %s "
+                            "with abstract methods %s" %
+                            (cls.__name__, ", ".join(sorted(am))))
+        return super(Abstract, cls).__new__(cls, *args, **kwds)
 
-class AbstractInstantiationError(TypeError):
 
-    """Exception raised when an abstract class is instantiated."""
+### ORDERING ABCS ###
 
-    def __init__(self, abstract_methods):
-        TypeError.__init__(self)
-        self.abstract_methods = abstract_methods
 
-    def __str__(self):
-        msg = ", ".join(sorted(self.abstract_methods))
-        return "Can't instantiate class with abstract method(s) %s" % msg
+class PartiallyOrdered(Abstract):
 
-    def __repr__(self):
-        return "AbstractInstantiationError(%r)" % (self.abstract_methods,)
+    """Partial orderings define a consistent but incomplete < operator.
 
+    Invariant: a < b < c => a < c
 
-class Abstract(metaclass=AbstractClass):
+    It is possible that none of a < b, a == b, a > b hold.
+    """
 
-    """Base class to support the abstractmethod decorator.
+    @abstractmethod
+    def __lt__(self, other):
+        return NotImplemented
 
-    This implicitly sets the metaclass to AbstractClass.
+    def __le__(self, other):
+        if not isinstance(other, PartiallyOrdered):
+            return NotImplemented
+        # Note that bool(NotImplemented) is True!
+        return self == other or self.__lt__(other)
+
+    # It's not necessary to define __gt__ and __ge__; these will
+    # automatically be translated to __lt__ and __le__ calls with
+    # swapped arguments by the rich comparisons framework.
+
+
+class TotallyOrdered(PartiallyOrdered):
+
+    """Total orderings guarantee that all values are ordered.
+
+    E.g. for any two a and b, exactly one of a < b, a == b, a > b holds.
+
+    XXX What about float?  The properties of NaN make it strictly
+    PartiallyOrdered.  But having it TotallyOrdered makes more sense
+    for most practical purposes.
     """
 
 
-### BASICS ###
+### ONE TRICK PONIES ###
 
 
 class Hashable(Abstract):
@@ -144,13 +153,13 @@
 
 class Iterator(Iterable):
 
-    """An iterator has two methods, __iter__() and next()."""
+    """An iterator has two methods, __iter__() and __next__()."""
 
     @abstractmethod
-    def next(self):
+    def __next__(self):
         raise StopIteration
 
-    def __iter__(self):
+    def __iter__(self):  # Concrete!  This should always return self
         return self
 
 
@@ -158,10 +167,10 @@
 
     """Implementation detail used by Iterable.__iter__()."""
 
-    def next(self):
-        # This will call Iterator.next() and hence will raise StopIteration.
-        return super(_EmptyIterator, self).next()
-        # Or: return Iterator.next(self)
+    def __next__(self):
+        # This will call Iterator.__next__() which will raise StopIteration.
+        return super(_EmptyIterator, self).__next__()
+        # Or: return Iterator.__next__(self)
         # Or: raise StopIteration
 
 
@@ -171,6 +180,7 @@
     def __len__(self):
         return 0
 
+
 class Container(Abstract):
 
     """A container has a __contains__() method."""
@@ -179,22 +189,25 @@
     def __contains__(self, elem):
         return False
 
+class Searchable(Container):
 
-### SETS ###
-
+    """A container whose __contains__ accepts sequences too."""
 
-class BasicSet(Container, Iterable):
+    # XXX This is an experiment.  Is it worth distinguishing?
+    # Perhaps later, when we have type annotations so you can write
+    # Container[T], we can do this:
+    #
+    # class Container(Abstract):
+    #     def __contains__(self, val: T) -> bool: ...
+    #
+    # class Searchable(Container):
+    #     def __contains__(self, val: T | Sequence[T]) -> bool: ...
 
-    """A basic set is an iterable container.
 
-    It may not have a length though; it may be infinite!
-
-    XXX Do we care about potentially infinite sets, or sets of
-    indeterminate size?
-    """
+### SETS ###
 
 
-class Set(Container, Iterable, Sized):
+class Set(Sized, Iterable, Container, PartiallyOrdered):
 
     """A plain set is a finite, iterable container.
 
@@ -227,12 +240,17 @@
             return NotImplemented
         return len(self) == len(other) and self.__le__(other)
 
-    # XXX Should we define __ge__ and __gt__ too?
 
-    # XXX The following implementations of &, |, ^, - return frozen sets
-    # because we have to pick a concrete type.  They are allowed to
-    # return any subclass of Set (but Set is not a
-    # concrete implementation).
+class ComposableSet(Set):
+
+    # XXX The following implementations of &, |, ^, - return frozen
+    # sets because we have to pick a concrete type.  They are allowed
+    # to return any subclass of Set (but Set is not a concrete
+    # implementation).  We return frozen sets because returning set
+    # may mislead callers from assuming that these operations always
+    # return mutable sets.
+
+    # XXX Alternatively, we might make these abstract.
 
     def __and__(self, other):
         new = set(self)
@@ -255,7 +273,8 @@
         return frozenset(new)
 
 
-class HashableSet(Set, Hashable):
+# XXX Should this derive from Set instead of from ComposableSet?
+class HashableSet(ComposableSet, Hashable):
 
     def __hash__(self):
         """The hash value must match __eq__.
@@ -273,6 +292,68 @@
         return h
 
 
+# XXX Should this derive from Set instead of from ComposableSet?
+class MutableSet(ComposableSet):
+
+    @abstractmethod
+    def add(self, value):
+        """Return True if it was added, False if already there."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def discard(self, value):
+        """Return True if it was deleted, False if not there."""
+        raise NotImplementedError
+
+    @abstractmethod
+    def clear(self):
+        """Implementing this would be bad and slow, hence it's abstract."""
+        # XXX It's a pain to have to define this.  Maybe leave out?
+        raise NotImplementedError
+
+    def pop(self):
+        """Return the popped value.  Raise KeyError if empty."""
+        it = iter(self)
+        try:
+            value = it.__next__()
+        except StopIteration:
+            raise KeyError
+        self.discard(value)
+        return value
+
+    def toggle(self, value):
+        """Return True if it was added, False if deleted."""
+        # XXX This implementation is not thread-safe
+        if value in self:
+            self.discard(value)
+            return False
+        else:
+            self.add(value)
+            return True
+
+    def __ior__(self, it: Iterable):
+        for value in it:
+            self.add(value)
+        return self
+
+    def __iand__(self, c: Container):
+        for value in self:
+            if value not in c:
+                self.discard(value)
+        return self
+
+    def __ixor__(self, it: Iterable):
+        # This calls toggle(), so if that is overridded, we call the override
+        for value in it:
+            self.toggle(it)
+        return self
+
+    def __isub__(self, it: Iterable):
+        for value in it:
+            self.discard(value)
+        return self
+
+
 # class set(Set)
 # class frozenset(HashableSet)
 
@@ -489,7 +570,7 @@
 
 
 
-class Sequence(Sized, Iterable):
+class Sequence(Sized, Iterable, Container):
 
     """A minimal sequence.
 
@@ -520,6 +601,14 @@
             yield self[i]
             i += 1
 
+    def __contains__(self, value):
+        for val in self:
+            if val == value:
+                return True
+        return False
+
+    # XXX Do we want all or some of the following?
+
     def __reversed__(self):
         i = len(self)
         while i > 0:
@@ -527,6 +616,7 @@
             yield self[i]
 
     def index(self, value):
+        # XXX Should we add optional start/stop args?  Probably not.
         for i, elem in enumerate(self):
             if elem == value:
                 return i
@@ -547,6 +637,9 @@
         repeat = _index(repeat)
         return self.__class__(elem for i in range(repeat) for elem in self)
 
+    # XXX Should we derive from PartiallyOrdered or TotallyOrdered?
+    # That depends on the items.  What if the items aren't orderable?
+
     def __eq__(self, other):
         if not isinstance(other, Sequence):
             return NotImplemented

Modified: sandbox/trunk/abc/test_abc.py
==============================================================================
--- sandbox/trunk/abc/test_abc.py	(original)
+++ sandbox/trunk/abc/test_abc.py	Thu Apr 26 01:37:38 2007
@@ -13,10 +13,10 @@
             @abc.abstractmethod
             def foo(self): pass
             def bar(self): pass
-        self.assertRaises(abc.AbstractInstantiationError, C)
+        self.assertRaises(TypeError, C)
         class D(C):
             def bar(self): pass
-        self.assertRaises(abc.AbstractInstantiationError, D)
+        self.assertRaises(TypeError, D)
         class E(D):
             def foo(self): pass
         E()


More information about the Python-checkins mailing list