summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Lib/abc.py206
-rw-r--r--Lib/test/test_abc.py142
-rw-r--r--Lib/test/test_typechecks.py77
-rw-r--r--Objects/abstract.c40
4 files changed, 465 insertions, 0 deletions
diff --git a/Lib/abc.py b/Lib/abc.py
new file mode 100644
index 0000000..d80fabd
--- /dev/null
+++ b/Lib/abc.py
@@ -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 set([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
diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py
new file mode 100644
index 0000000..3fd9bde
--- /dev/null
+++ b/Lib/test/test_abc.py
@@ -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()
diff --git a/Lib/test/test_typechecks.py b/Lib/test/test_typechecks.py
new file mode 100644
index 0000000..21be62d
--- /dev/null
+++ b/Lib/test/test_typechecks.py
@@ -0,0 +1,77 @@
+"""Unit tests for __instancecheck__ and __subclasscheck__."""
+
+import unittest
+from test import test_support
+
+
+class ABC(type):
+
+ def __instancecheck__(cls, inst):
+ """Implement isinstance(inst, cls)."""
+ return any(cls.__subclasscheck__(c)
+ for c in set([type(inst), inst.__class__]))
+
+ def __subclasscheck__(cls, sub):
+ """Implement issubclass(sub, cls)."""
+ candidates = cls.__dict__.get("__subclass__", set()) | set([cls])
+ return any(c in candidates for c in sub.mro())
+
+
+class Integer:
+
+ __metaclass__ = ABC
+
+ __subclass__ = set([int])
+
+
+class SubInt(Integer):
+
+ pass
+
+
+class Evil:
+ def __instancecheck__(self, inst): return False
+
+
+class TypeChecksTest(unittest.TestCase):
+
+ def testIsSubclassInternal(self):
+ self.assertEqual(Integer.__subclasscheck__(int), True)
+ self.assertEqual(Integer.__subclasscheck__(float), False)
+
+ def testIsSubclassBuiltin(self):
+ self.assertEqual(issubclass(int, Integer), True)
+ self.assertEqual(issubclass(float, Integer), False)
+
+ def testIsInstanceBuiltin(self):
+ self.assertEqual(isinstance(42, Integer), True)
+ self.assertEqual(isinstance(3.14, Integer), False)
+
+ def testIsInstanceActual(self):
+ self.assertEqual(isinstance(Integer(), Integer), True)
+
+ def testIsSubclassActual(self):
+ self.assertEqual(issubclass(Integer, Integer), True)
+
+ def testSubclassBehavior(self):
+ self.assertEqual(issubclass(SubInt, Integer), True)
+ self.assertEqual(issubclass(SubInt, SubInt), True)
+ self.assertEqual(issubclass(Integer, SubInt), False)
+ self.assertEqual(issubclass(int, SubInt), False)
+ self.assertEqual(isinstance(SubInt(), Integer), True)
+ self.assertEqual(isinstance(SubInt(), SubInt), True)
+ self.assertEqual(isinstance(42, SubInt), False)
+
+ def testInfiniteRecursionCaughtProperly(self):
+ e = Evil()
+ # This invokes isinstance() recursively, until the stack is exhausted.
+ self.assertRaises(RuntimeError, isinstance, e, Evil)
+ # XXX How to check the same situation for issubclass()?
+
+
+def test_main():
+ test_support.run_unittest(TypeChecksTest)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/Objects/abstract.c b/Objects/abstract.c
index 7378e0d..16e0270 100644
--- a/Objects/abstract.c
+++ b/Objects/abstract.c
@@ -2279,6 +2279,27 @@ recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth)
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());
}
@@ -2334,6 +2355,25 @@ recursive_issubclass(PyObject *derived, PyObject *cls, int recursion_depth)
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());
}