diff -r 74d182cf0187 Doc/library/abc.rst --- a/Doc/library/abc.rst Wed Nov 30 00:25:06 2011 +0200 +++ b/Doc/library/abc.rst Wed Dec 07 08:04:02 2011 -0500 @@ -127,19 +127,18 @@ available as a method of ``Foo``, so it is provided separately. -It also provides the following decorators: +The :mod:`abc` module also provides the following decorators: .. decorator:: abstractmethod(function) A decorator indicating abstract methods. - Using this decorator requires that the class's metaclass is :class:`ABCMeta` or - is derived from it. - A class that has a metaclass derived from :class:`ABCMeta` - cannot be instantiated unless all of its abstract methods and - properties are overridden. - The abstract methods can be called using any of the normal 'super' call - mechanisms. + Using this decorator requires that the class's metaclass is :class:`ABCMeta` + or is derived from it. A class that has a metaclass derived from + :class:`ABCMeta` cannot be instantiated unless all of its abstract methods + and properties are overridden. The abstract methods can be called using any + of the normal 'super' call mechanisms. :func:`abstractmethod` may be used + to declare abstract methods for properties and descriptors. Dynamically adding abstract methods to a class, or attempting to modify the abstraction status of a method or class once it is created, are not @@ -147,12 +146,52 @@ regular inheritance; "virtual subclasses" registered with the ABC's :meth:`register` method are not affected. - Usage:: + When :func:`abstractmethod` is applied in combination with other method + descriptors, it should be applied as the innermost decorator, as shown in + the following usage examples:: class C(metaclass=ABCMeta): @abstractmethod def my_abstract_method(self, ...): ... + @classmethod + @abstractmethod + def my_abstract_classmethod(cls, ...): + ... + @staticmethod + @abstractmethod + def my_abstract_staticmethod(...): + ... + + @property + @abstractmethod + def my_abstract_property(self): + ... + @my_abstract_property.setter + @abstractmethod + def my_abstract_property(self, val): + ... + + @abstractmethod + def _get_x(self): + ... + @abstractmethod + def _set_x(self, val): + ... + x = property(_get_x, _set_x) + + In order to correctly interoperate with the abstract base class machinery, + the descriptor must identify itself as abstract using + :attr:`__isabstractmethod__`. In general, this attribute should be ``True`` + if any of the methods used to compose the descriptor are abstract. For + example, Python's built-in property does the equivalent of:: + + class Descriptor: + ... + @property + def __isabstractmethod__(self): + return any(getattr(f, '__isabstractmethod__', False) for + f in (self._fget, self._fset, self._fdel)) .. note:: @@ -177,6 +216,8 @@ ... .. versionadded:: 3.2 + .. deprecated:: 3.3 + Use :class:`classmethod` with :func:`abstractmethod` instead .. decorator:: abstractstaticmethod(function) @@ -192,18 +233,19 @@ ... .. versionadded:: 3.2 + .. deprecated:: 3.3 + Use :class:`staticmethod` with :func:`abstractmethod` instead .. decorator:: abstractproperty(fget=None, fset=None, fdel=None, doc=None) A subclass of the built-in :func:`property`, indicating an abstract property. - Using this function requires that the class's metaclass is :class:`ABCMeta` or - is derived from it. - A class that has a metaclass derived from :class:`ABCMeta` cannot be - instantiated unless all of its abstract methods and properties are overridden. - The abstract properties can be called using any of the normal - 'super' call mechanisms. + Using this function requires that the class's metaclass is :class:`ABCMeta` + or is derived from it. A class that has a metaclass derived from + :class:`ABCMeta` cannot be instantiated unless all of its abstract methods + and properties are overridden. The abstract properties can be called using + any of the normal 'super' call mechanisms. Usage:: @@ -220,6 +262,9 @@ def setx(self, value): ... x = abstractproperty(getx, setx) + .. deprecated:: 3.3 + Use :class:`property` with :func:`abstractmethod` instead + .. rubric:: Footnotes diff -r 74d182cf0187 Doc/whatsnew/3.3.rst --- a/Doc/whatsnew/3.3.rst Wed Nov 30 00:25:06 2011 +0200 +++ b/Doc/whatsnew/3.3.rst Wed Dec 07 08:04:02 2011 -0500 @@ -352,6 +352,23 @@ (Contributed by IƱigo Serna in :issue:`6755`) +abc +--- + +Improved support for abstract base classes containing descriptors composed with +abstract methods. The recommended approach to declaring abstract descriptors is +now to provide :attr:`__isabstractmethod__` as a dynamically updated +property. The built-in descriptors have been updated accordingly. + + * :class:`abc.abstractproperty` has been deprecated, use :class:`property` + with :func:`abc.abstractmethod` instead. + * :class:`abc.abstractclassmethod` has been deprecated, use + :class:`classmethod` with :func:`abc.abstractmethod` instead. + * :class:`abc.abstractstaticmethod` has been deprecated, use + :class:`property` with :func:`abc.abstractmethod` instead. + +(Contributed by Darren Dale in :issue:`11610`) + faulthandler ------------ diff -r 74d182cf0187 Include/object.h --- a/Include/object.h Wed Nov 30 00:25:06 2011 +0200 +++ b/Include/object.h Wed Dec 07 08:04:02 2011 -0500 @@ -473,6 +473,7 @@ PyAPI_FUNC(PyObject *) PyObject_GetAttr(PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_SetAttr(PyObject *, PyObject *, PyObject *); PyAPI_FUNC(int) PyObject_HasAttr(PyObject *, PyObject *); +PyAPI_FUNC(int) _PyObject_IsAbstract(PyObject *); PyAPI_FUNC(PyObject *) _PyObject_GetAttrId(PyObject *, struct _Py_Identifier *); PyAPI_FUNC(int) _PyObject_SetAttrId(PyObject *, struct _Py_Identifier *, PyObject *); PyAPI_FUNC(int) _PyObject_HasAttrId(PyObject *, struct _Py_Identifier *); diff -r 74d182cf0187 Lib/abc.py --- a/Lib/abc.py Wed Nov 30 00:25:06 2011 +0200 +++ b/Lib/abc.py Wed Dec 07 08:04:02 2011 -0500 @@ -26,7 +26,8 @@ class abstractclassmethod(classmethod): - """A decorator indicating abstract classmethods. + """ + A decorator indicating abstract classmethods. Similar to abstractmethod. @@ -36,6 +37,9 @@ @abstractclassmethod def my_abstract_classmethod(cls, ...): ... + + 'abstractclassmethod' is deprecated. Use 'classmethod' with + 'abstractmethod' instead. """ __isabstractmethod__ = True @@ -46,7 +50,8 @@ class abstractstaticmethod(staticmethod): - """A decorator indicating abstract staticmethods. + """ + A decorator indicating abstract staticmethods. Similar to abstractmethod. @@ -56,6 +61,9 @@ @abstractstaticmethod def my_abstract_staticmethod(...): ... + + 'abstractstaticmethod' is deprecated. Use 'staticmethod' with + 'abstractmethod' instead. """ __isabstractmethod__ = True @@ -66,7 +74,8 @@ class abstractproperty(property): - """A decorator indicating abstract properties. + """ + 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 @@ -88,7 +97,11 @@ def getx(self): ... def setx(self, value): ... x = abstractproperty(getx, setx) + + 'abstractproperty' is deprecated. Use 'property' with 'abstractmethod' + instead. """ + __isabstractmethod__ = True diff -r 74d182cf0187 Lib/numbers.py --- a/Lib/numbers.py Wed Nov 30 00:25:06 2011 +0200 +++ b/Lib/numbers.py Wed Dec 07 08:04:02 2011 -0500 @@ -5,7 +5,7 @@ TODO: Fill out more detailed documentation on the operators.""" -from abc import ABCMeta, abstractmethod, abstractproperty +from abc import ABCMeta, abstractmethod __all__ = ["Number", "Complex", "Real", "Rational", "Integral"] @@ -50,7 +50,8 @@ """True if self != 0. Called for bool(self).""" return self != 0 - @abstractproperty + @property + @abstractmethod def real(self): """Retrieve the real component of this number. @@ -58,7 +59,8 @@ """ raise NotImplementedError - @abstractproperty + @property + @abstractmethod def imag(self): """Retrieve the imaginary component of this number. @@ -272,11 +274,13 @@ __slots__ = () - @abstractproperty + @property + @abstractmethod def numerator(self): raise NotImplementedError - @abstractproperty + @property + @abstractmethod def denominator(self): raise NotImplementedError diff -r 74d182cf0187 Lib/test/test_abc.py --- a/Lib/test/test_abc.py Wed Nov 30 00:25:06 2011 +0200 +++ b/Lib/test/test_abc.py Wed Dec 07 08:04:02 2011 -0500 @@ -10,14 +10,7 @@ from inspect import isabstract -class TestABC(unittest.TestCase): - - def test_abstractmethod_basics(self): - @abc.abstractmethod - def foo(self): pass - self.assertTrue(foo.__isabstractmethod__) - def bar(self): pass - self.assertFalse(hasattr(bar, "__isabstractmethod__")) +class TestLegacyAPI(unittest.TestCase): def test_abstractproperty_basics(self): @abc.abstractproperty @@ -29,10 +22,12 @@ class C(metaclass=abc.ABCMeta): @abc.abstractproperty def foo(self): return 3 + self.assertRaises(TypeError, C) class D(C): @property def foo(self): return super().foo self.assertEqual(D().foo, 3) + self.assertFalse(getattr(D.foo, "__isabstractmethod__", False)) def test_abstractclassmethod_basics(self): @abc.abstractclassmethod @@ -40,7 +35,7 @@ self.assertTrue(foo.__isabstractmethod__) @classmethod def bar(cls): pass - self.assertFalse(hasattr(bar, "__isabstractmethod__")) + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) class C(metaclass=abc.ABCMeta): @abc.abstractclassmethod @@ -58,7 +53,7 @@ self.assertTrue(foo.__isabstractmethod__) @staticmethod def bar(): pass - self.assertFalse(hasattr(bar, "__isabstractmethod__")) + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) class C(metaclass=abc.ABCMeta): @abc.abstractstaticmethod @@ -98,6 +93,163 @@ self.assertRaises(TypeError, F) # because bar is abstract now self.assertTrue(isabstract(F)) + +class TestABC(unittest.TestCase): + + def test_abstractmethod_basics(self): + @abc.abstractmethod + def foo(self): pass + self.assertTrue(foo.__isabstractmethod__) + def bar(self): pass + self.assertFalse(hasattr(bar, "__isabstractmethod__")) + + def test_abstractproperty_basics(self): + @property + @abc.abstractmethod + def foo(self): pass + self.assertTrue(foo.__isabstractmethod__) + def bar(self): pass + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) + + class C(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def foo(self): return 3 + self.assertRaises(TypeError, C) + class D(C): + @C.foo.getter + def foo(self): return super().foo + self.assertEqual(D().foo, 3) + + def test_abstractclassmethod_basics(self): + @classmethod + @abc.abstractmethod + def foo(cls): pass + self.assertTrue(foo.__isabstractmethod__) + @classmethod + def bar(cls): pass + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) + + class C(metaclass=abc.ABCMeta): + @classmethod + @abc.abstractmethod + def foo(cls): return cls.__name__ + self.assertRaises(TypeError, C) + class D(C): + @classmethod + def foo(cls): return super().foo() + self.assertEqual(D.foo(), 'D') + self.assertEqual(D().foo(), 'D') + + def test_abstractstaticmethod_basics(self): + @staticmethod + @abc.abstractmethod + def foo(): pass + self.assertTrue(foo.__isabstractmethod__) + @staticmethod + def bar(): pass + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) + + class C(metaclass=abc.ABCMeta): + @staticmethod + @abc.abstractmethod + def foo(): return 3 + self.assertRaises(TypeError, C) + class D(C): + @staticmethod + def foo(): return 4 + self.assertEqual(D.foo(), 4) + self.assertEqual(D().foo(), 4) + + def test_abstractmethod_integration(self): + for abstractthing in [abc.abstractmethod, abc.abstractproperty, + abc.abstractclassmethod, + abc.abstractstaticmethod]: + class C(metaclass=abc.ABCMeta): + @abstractthing + def foo(self): pass # abstract + def bar(self): pass # concrete + self.assertEqual(C.__abstractmethods__, {"foo"}) + self.assertRaises(TypeError, C) # because foo is abstract + self.assertTrue(isabstract(C)) + class D(C): + def bar(self): pass # concrete override of concrete + self.assertEqual(D.__abstractmethods__, {"foo"}) + self.assertRaises(TypeError, D) # because foo is still abstract + self.assertTrue(isabstract(D)) + class E(D): + def foo(self): pass + self.assertEqual(E.__abstractmethods__, set()) + E() # now foo is concrete, too + self.assertFalse(isabstract(E)) + class F(E): + @abstractthing + def bar(self): pass # abstract override of concrete + self.assertEqual(F.__abstractmethods__, {"bar"}) + self.assertRaises(TypeError, F) # because bar is abstract now + self.assertTrue(isabstract(F)) + + def test_descriptors_with_abstractmethod(self): + class C(metaclass=abc.ABCMeta): + @property + @abc.abstractmethod + def foo(self): return 3 + @foo.setter + @abc.abstractmethod + def foo(self, val): pass + self.assertRaises(TypeError, C) + class D(C): + @C.foo.getter + def foo(self): return super().foo + self.assertRaises(TypeError, D) + class E(D): + @D.foo.setter + def foo(self, val): pass + self.assertEqual(E().foo, 3) + # check that the property's __isabstractmethod__ descriptor does the + # right thing when presented with a value that fails truth testing: + class NotBool(object): + def __nonzero__(self): + raise ValueError() + __len__ = __nonzero__ + with self.assertRaises(ValueError): + class F(C): + def bar(self): + pass + bar.__isabstractmethod__ = NotBool() + foo = property(bar) + + + def test_customdescriptors_with_abstractmethod(self): + class Descriptor: + def __init__(self, fget, fset=None): + self._fget = fget + self._fset = fset + def getter(self, callable): + return Descriptor(callable, self._fget) + def setter(self, callable): + return Descriptor(self._fget, callable) + @property + def __isabstractmethod__(self): + return (getattr(self._fget, '__isabstractmethod__', False) + or getattr(self._fset, '__isabstractmethod__', False)) + class C(metaclass=abc.ABCMeta): + @Descriptor + @abc.abstractmethod + def foo(self): return 3 + @foo.setter + @abc.abstractmethod + def foo(self, val): pass + self.assertRaises(TypeError, C) + class D(C): + @C.foo.getter + def foo(self): return super().foo + self.assertRaises(TypeError, D) + class E(D): + @D.foo.setter + def foo(self, val): pass + self.assertFalse(E.foo.__isabstractmethod__) + def test_metaclass_abc(self): # Metaclasses can be ABCs, too. class A(metaclass=abc.ABCMeta): diff -r 74d182cf0187 Lib/test/test_property.py --- a/Lib/test/test_property.py Wed Nov 30 00:25:06 2011 +0200 +++ b/Lib/test/test_property.py Wed Dec 07 08:04:02 2011 -0500 @@ -128,6 +128,29 @@ self.assertEqual(newgetter.spam, 8) self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring") + def test_property___isabstractmethod__descriptor(self): + for val in (True, False, [], [1], '', '1'): + class C(object): + def foo(self): + pass + foo.__isabstractmethod__ = val + foo = property(foo) + self.assertIs(C.foo.__isabstractmethod__, bool(val)) + + # check that the property's __isabstractmethod__ descriptor does the + # right thing when presented with a value that fails truth testing: + class NotBool(object): + def __nonzero__(self): + raise ValueError() + __len__ = __nonzero__ + with self.assertRaises(ValueError): + class C(object): + def foo(self): + pass + foo.__isabstractmethod__ = NotBool() + foo = property(foo) + C.foo.__isabstractmethod__ + # Issue 5890: subclasses of property do not preserve method __doc__ strings class PropertySub(property): diff -r 74d182cf0187 Misc/ACKS --- a/Misc/ACKS Wed Nov 30 00:25:06 2011 +0200 +++ b/Misc/ACKS Wed Dec 07 08:04:02 2011 -0500 @@ -220,6 +220,7 @@ Antonio Cuni Brian Curtin Lisandro Dalcin +Darren Dale Andrew Dalke Lars Damerow Evan Dandrea diff -r 74d182cf0187 Objects/descrobject.c --- a/Objects/descrobject.c Wed Nov 30 00:25:06 2011 +0200 +++ b/Objects/descrobject.c Wed Dec 07 08:04:02 2011 -0500 @@ -1329,6 +1329,43 @@ return 0; } +static PyObject * +property_get___isabstractmethod__(propertyobject *prop, void *closure) +{ + int res = _PyObject_IsAbstract(prop->prop_get); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + + res = _PyObject_IsAbstract(prop->prop_set); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + + res = _PyObject_IsAbstract(prop->prop_del); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyGetSetDef property_getsetlist[] = { + {"__isabstractmethod__", + (getter)property_get___isabstractmethod__, NULL, + NULL, + NULL}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(property_doc, "property(fget=None, fset=None, fdel=None, doc=None) -> property attribute\n" "\n" @@ -1394,7 +1431,7 @@ 0, /* tp_iternext */ property_methods, /* tp_methods */ property_members, /* tp_members */ - 0, /* tp_getset */ + property_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ property_descr_get, /* tp_descr_get */ diff -r 74d182cf0187 Objects/funcobject.c --- a/Objects/funcobject.c Wed Nov 30 00:25:06 2011 +0200 +++ b/Objects/funcobject.c Wed Dec 07 08:04:02 2011 -0500 @@ -814,6 +814,27 @@ {NULL} /* Sentinel */ }; +static PyObject * +cm_get___isabstractmethod__(classmethod *cm, void *closure) +{ + int res = _PyObject_IsAbstract(cm->cm_callable); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyGetSetDef cm_getsetlist[] = { + {"__isabstractmethod__", + (getter)cm_get___isabstractmethod__, NULL, + NULL, + NULL}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(classmethod_doc, "classmethod(function) -> method\n\ \n\ @@ -865,7 +886,7 @@ 0, /* tp_iternext */ 0, /* tp_methods */ cm_memberlist, /* tp_members */ - 0, /* tp_getset */ + cm_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ cm_descr_get, /* tp_descr_get */ @@ -969,6 +990,27 @@ {NULL} /* Sentinel */ }; +static PyObject * +sm_get___isabstractmethod__(staticmethod *sm, void *closure) +{ + int res = _PyObject_IsAbstract(sm->sm_callable); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; +} + +static PyGetSetDef sm_getsetlist[] = { + {"__isabstractmethod__", + (getter)sm_get___isabstractmethod__, NULL, + NULL, + NULL}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(staticmethod_doc, "staticmethod(function) -> method\n\ \n\ @@ -1017,7 +1059,7 @@ 0, /* tp_iternext */ 0, /* tp_methods */ sm_memberlist, /* tp_members */ - 0, /* tp_getset */ + sm_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ sm_descr_get, /* tp_descr_get */ diff -r 74d182cf0187 Objects/object.c --- a/Objects/object.c Wed Nov 30 00:25:06 2011 +0200 +++ b/Objects/object.c Wed Dec 07 08:04:02 2011 -0500 @@ -830,6 +830,29 @@ return res; } +int +_PyObject_IsAbstract(PyObject *obj) +{ + int res; + PyObject* isabstract; + _Py_IDENTIFIER(__isabstractmethod__); + + if (obj == NULL) + return 0; + + isabstract = _PyObject_GetAttrId(obj, &PyId___isabstractmethod__); + if (isabstract == NULL) { + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; + } + res = PyObject_IsTrue(isabstract); + Py_DECREF(isabstract); + return res; +} + PyObject * _PyObject_GetAttrId(PyObject *v, _Py_Identifier *name) {