Index: Objects/abstract.c =================================================================== --- Objects/abstract.c (revision 57506) +++ Objects/abstract.c (working copy) @@ -2275,6 +2275,27 @@ int PyObject_IsInstance(PyObject *inst, PyObject *cls) { + PyObject *t, *v, *tb; + PyObject *checker; + PyErr_Fetch(&t, &v, &tb); + checker = PyObject_GetAttrString(cls, "__instancecheck__"); + PyErr_Restore(t, v, tb); + if (checker != NULL) { + PyObject *res; + int ok = -1; + if (Py_EnterRecursiveCall(" in __instancecheck__")) { + Py_DECREF(checker); + return ok; + } + res = PyObject_CallFunctionObjArgs(checker, inst, NULL); + Py_LeaveRecursiveCall(); + Py_DECREF(checker); + if (res != NULL) { + ok = PyObject_IsTrue(res); + Py_DECREF(res); + } + return ok; + } return recursive_isinstance(inst, cls, Py_GetRecursionLimit()); } @@ -2330,6 +2351,25 @@ int PyObject_IsSubclass(PyObject *derived, PyObject *cls) { + PyObject *t, *v, *tb; + PyObject *checker; + PyErr_Fetch(&t, &v, &tb); + checker = PyObject_GetAttrString(cls, "__subclasscheck__"); + PyErr_Restore(t, v, tb); + if (checker != NULL) { + PyObject *res; + int ok = -1; + if (Py_EnterRecursiveCall(" in __subclasscheck__")) + return ok; + res = PyObject_CallFunctionObjArgs(checker, derived, NULL); + Py_LeaveRecursiveCall(); + Py_DECREF(checker); + if (res != NULL) { + ok = PyObject_IsTrue(res); + Py_DECREF(res); + } + return ok; + } return recursive_issubclass(derived, cls, Py_GetRecursionLimit()); } Index: Lib/abc.py =================================================================== --- Lib/abc.py (revision 0) +++ Lib/abc.py (revision 0) @@ -0,0 +1,206 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Abstract Base Classes (ABCs) according to PEP 3119.""" + + +def abstractmethod(funcobj): + """A decorator indicating abstract methods. + + Requires that the metaclass is ABCMeta or derived from it. A + class that has a metaclass derived from ABCMeta cannot be + instantiated unless all of its abstract methods are overridden. + The abstract methods can be called using any of the the normal + 'super' call mechanisms. + + Usage: + + class C(metaclass=ABCMeta): + @abstractmethod + def my_abstract_method(self, ...): + ... + """ + funcobj.__isabstractmethod__ = True + return funcobj + + +class abstractproperty(property): + """A decorator indicating abstract properties. + + Requires that the metaclass is ABCMeta or derived from it. A + class that has a metaclass derived from ABCMeta cannot be + instantiated unless all of its abstract properties are overridden. + The abstract properties can be called using any of the the normal + 'super' call mechanisms. + + Usage: + + class C(metaclass=ABCMeta): + @abstractproperty + def my_abstract_property(self): + ... + + This defines a read-only property; you can also define a read-write + abstract property using the 'long' form of property declaration: + + class C(metaclass=ABCMeta): + def getx(self): ... + def setx(self, value): ... + x = abstractproperty(getx, setx) + """ + __isabstractmethod__ = True + + +class _Abstract(object): + + """Helper class inserted into the bases by ABCMeta (using _fix_bases()). + + You should never need to explicitly subclass this class. + + There should never be a base class between _Abstract and object. + """ + + 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)))) + if (args or kwds) and cls.__init__ is object.__init__: + raise TypeError("Can't pass arguments to __new__ " + "without overriding __init__") + return object.__new__(cls) + + @classmethod + def __subclasshook__(cls, subclass): + """Abstract classes can override this to customize issubclass(). + + This is invoked early on by __subclasscheck__() below. It + should return True, False or NotImplemented. If it returns + NotImplemented, the normal algorithm is used. Otherwise, it + overrides the normal algorithm (and the outcome is cached). + """ + return NotImplemented + + +def _fix_bases(bases): + """Helper method that inserts _Abstract in the bases if needed.""" + for base in bases: + if issubclass(base, _Abstract): + # _Abstract is already a base (maybe indirectly) + return bases + if object in bases: + # Replace object with _Abstract + return tuple([_Abstract if base is object else base + for base in bases]) + # Append _Abstract to the end + return bases + (_Abstract,) + + +class ABCMeta(type): + + """Metaclass for defining Abstract Base Classes (ABCs). + + Use this metaclass to create an ABC. An ABC can be subclassed + directly, and then acts as a mix-in class. You can also register + unrelated concrete classes (even built-in classes) and unrelated + ABCs as 'virtual subclasses' -- these and their descendants will + be considered subclasses of the registering ABC by the built-in + issubclass() function, but the registering ABC won't show up in + their MRO (Method Resolution Order) nor will method + implementations defined by the registering ABC be callable (not + even via super()). + + """ + + # A global counter that is incremented each time a class is + # registered as a virtual subclass of anything. It forces the + # negative cache to be cleared before its next use. + _abc_invalidation_counter = 0 + + def __new__(mcls, name, bases, namespace): + bases = _fix_bases(bases) + cls = super(ABCMeta, mcls).__new__(mcls, name, bases, namespace) + # Compute set of abstract method names + abstracts = set(name + for name, value in namespace.items() + if getattr(value, "__isabstractmethod__", False)) + for base in bases: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = abstracts + # Set up inheritance registry + cls._abc_registry = set() + cls._abc_cache = set() + cls._abc_negative_cache = set() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + return cls + + def register(cls, subclass): + """Register a virtual subclass of an ABC.""" + if not isinstance(cls, type): + raise TypeError("Can only register classes") + if issubclass(subclass, cls): + return # Already a subclass + # Subtle: test for cycles *after* testing for "already a subclass"; + # this means we allow X.register(X) and interpret it as a no-op. + if issubclass(cls, subclass): + # This would create a cycle, which is bad for the algorithm below + raise RuntimeError("Refusing to create an inheritance cycle") + cls._abc_registry.add(subclass) + ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache + + def _dump_registry(cls, file=None): + """Debug helper to print the ABC registry.""" + print >> file, "Class: %s.%s" % (cls.__module__, cls.__name__) + print >> file, "Inv.counter: %s" % ABCMeta._abc_invalidation_counter + for name in sorted(cls.__dict__.keys()): + if name.startswith("_abc_"): + value = getattr(cls, name) + print >> file, "%s: %r" % (name, value) + + def __instancecheck__(cls, instance): + """Override for isinstance(instance, cls).""" + return any(cls.__subclasscheck__(c) + for c in [instance.__class__, type(instance)]) + + def __subclasscheck__(cls, subclass): + """Override for issubclass(subclass, cls).""" + # Check cache + if subclass in cls._abc_cache: + return True + # Check negative cache; may have to invalidate + if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + # Invalidate the negative cache + cls._abc_negative_cache = set() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in cls._abc_negative_cache: + return False + # Check the subclass hook + ok = cls.__subclasshook__(subclass) + if ok is not NotImplemented: + assert isinstance(ok, bool) + if ok: + cls._abc_cache.add(subclass) + else: + cls._abc_negative_cache.add(subclass) + return ok + # Check if it's a direct subclass + if cls in subclass.__mro__: + cls._abc_cache.add(subclass) + return True + # Check if it's a subclass of a registered class (recursive) + for rcls in cls._abc_registry: + if issubclass(subclass, rcls): + cls._abc_registry.add(subclass) + return True + # Check if it's a subclass of a subclass (recursive) + for scls in cls.__subclasses__(): + if issubclass(subclass, scls): + cls._abc_registry.add(subclass) + return True + # No dice; update negative cache + cls._abc_negative_cache.add(subclass) + return False Index: Lib/test/test_abc.py =================================================================== --- Lib/test/test_abc.py (revision 0) +++ Lib/test/test_abc.py (revision 0) @@ -0,0 +1,142 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Unit tests for abc.py.""" + +import sys +import unittest +from test import test_support + +import abc +__metaclass__ = type + + +class TestABC(unittest.TestCase): + + def test_abstractmethod_basics(self): + @abc.abstractmethod + def foo(self): pass + self.assertEqual(foo.__isabstractmethod__, True) + def bar(self): pass + self.assertEqual(hasattr(bar, "__isabstractmethod__"), False) + + def test_abstractproperty_basics(self): + @abc.abstractproperty + def foo(self): pass + self.assertEqual(foo.__isabstractmethod__, True) + def bar(self): pass + self.assertEqual(hasattr(bar, "__isabstractmethod__"), False) + + class C: + __metaclass__ = abc.ABCMeta + @abc.abstractproperty + def foo(self): return 3 + class D(C): + @property + def foo(self): return super(D, self).foo + self.assertEqual(D().foo, 3) + + def test_abstractmethod_integration(self): + for abstractthing in [abc.abstractmethod, abc.abstractproperty]: + class C: + __metaclass__ = abc.ABCMeta + @abstractthing + def foo(self): pass # abstract + def bar(self): pass # concrete + self.assertEqual(C.__abstractmethods__, set(["foo"])) + self.assertRaises(TypeError, C) # because foo is abstract + class D(C): + def bar(self): pass # concrete override of concrete + self.assertEqual(D.__abstractmethods__, set(["foo"])) + self.assertRaises(TypeError, D) # because foo is still abstract + class E(D): + def foo(self): pass + self.assertEqual(E.__abstractmethods__, set()) + E() # now foo is concrete, too + class F(E): + @abstractthing + def bar(self): pass # abstract override of concrete + self.assertEqual(F.__abstractmethods__, set(["bar"])) + self.assertRaises(TypeError, F) # because bar is abstract now + + def test_registration_basics(self): + class A: + __metaclass__ = abc.ABCMeta + class B: + pass + b = B() + self.assertEqual(issubclass(B, A), False) + self.assertEqual(isinstance(b, A), False) + A.register(B) + self.assertEqual(issubclass(B, A), True) + self.assertEqual(isinstance(b, A), True) + class C(B): + pass + c = C() + self.assertEqual(issubclass(C, A), True) + self.assertEqual(isinstance(c, A), True) + + def test_registration_builtins(self): + class A: + __metaclass__ = abc.ABCMeta + A.register(int) + self.assertEqual(isinstance(42, A), True) + self.assertEqual(issubclass(int, A), True) + class B(A): + pass + B.register(basestring) + self.assertEqual(isinstance("", A), True) + self.assertEqual(issubclass(str, A), True) + + def test_registration_edge_cases(self): + class A: + __metaclass__ = abc.ABCMeta + A.register(A) # should pass silently + class A1(A): + pass + self.assertRaises(RuntimeError, A1.register, A) # cycles not allowed + class B: + pass + A1.register(B) # ok + A1.register(B) # should pass silently + class C(A): + pass + A.register(C) # should pass silently + self.assertRaises(RuntimeError, C.register, A) # cycles not allowed + C.register(B) # ok + + def test_registration_transitiveness(self): + class A: + __metaclass__ = abc.ABCMeta + self.failUnless(issubclass(A, A)) + class B: + __metaclass__ = abc.ABCMeta + self.failIf(issubclass(A, B)) + self.failIf(issubclass(B, A)) + class C: + __metaclass__ = abc.ABCMeta + A.register(B) + class B1(B): + pass + self.failUnless(issubclass(B1, A)) + class C1(C): + pass + B1.register(C1) + self.failIf(issubclass(C, B)) + self.failIf(issubclass(C, B1)) + self.failUnless(issubclass(C1, A)) + self.failUnless(issubclass(C1, B)) + self.failUnless(issubclass(C1, B1)) + C1.register(int) + class MyInt(int): + pass + self.failUnless(issubclass(MyInt, A)) + self.failUnless(isinstance(42, A)) + + +def test_main(): + test_support.run_unittest(TestABC) + + +if __name__ == "__main__": + unittest.main()