[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


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 ###

-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!

+### 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)

+class ComposableSet(Set): +

@@ -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): +

@@ -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

@@ -527,6 +616,7 @@ yield self[i]

 def index(self, value):

@@ -547,6 +637,9 @@ repeat = _index(repeat) return self.class(elem for i in range(repeat) for elem in self)

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



More information about the Python-checkins mailing list