[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