[Python-checkins] r54976 - sandbox/trunk/abc/abc.py sandbox/trunk/abc/test_abc.py (original) (raw)
guido.van.rossum python-checkins at python.org
Thu Apr 26 01:37:42 CEST 2007
- Previous message: [Python-checkins] r54975 - peps/trunk/pep-3119.txt
- Next message: [Python-checkins] buildbot warnings in alpha Debian trunk
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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.
def and(self, other): new = set(self) XXX Alternatively, we might make these abstract.
@@ -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()
- Previous message: [Python-checkins] r54975 - peps/trunk/pep-3119.txt
- Next message: [Python-checkins] buildbot warnings in alpha Debian trunk
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]