diff -r e572a97a1bd1 Doc/library/abc.rst --- a/Doc/library/abc.rst Fri Jun 10 19:05:16 2011 +0100 +++ b/Doc/library/abc.rst Sat Jul 23 12:32:53 2011 -0400 @@ -127,19 +127,18 @@ available as a method of ``Foo``, so it is provided separately. -It also provides the following decorators: +The 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 @@ -153,6 +152,47 @@ @abstractmethod def my_abstract_method(self, ...): ... + @classmethod + @abstractmethod + def my_abstract_method(self, ...): + ... + @staticmethod + @abstractmethod + def my_abstract_method(self, ...): + ... + + @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) + + + .. note:: + + In order to include abc support for custom descriptors, the descriptor + must identify itself as abstract using :attr:`__isabstractmethod__`. For + example, Python's built-in property does the equivalent of:: + + class Descriptor: + ... + @property + def __isabstractmethod__(self): + for f in (self._fget, self._fset, self._fdel): + is_abstract = getattr(f, '__isabstractmethod__', False) + if is_abstract: return True + return False .. note:: @@ -166,59 +206,20 @@ .. decorator:: abstractclassmethod(function) - A subclass of the built-in :func:`classmethod`, indicating an abstract - classmethod. Otherwise it is similar to :func:`abstractmethod`. - - Usage:: - - class C(metaclass=ABCMeta): - @abstractclassmethod - def my_abstract_classmethod(cls, ...): - ... - - .. versionadded:: 3.2 + .. deprecated:: 3.3 + Use :class:`classmethod` and :func:`abstractmethod` instead .. decorator:: abstractstaticmethod(function) - A subclass of the built-in :func:`staticmethod`, indicating an abstract - staticmethod. Otherwise it is similar to :func:`abstractmethod`. - - Usage:: - - class C(metaclass=ABCMeta): - @abstractstaticmethod - def my_abstract_staticmethod(...): - ... - - .. versionadded:: 3.2 + .. deprecated:: 3.3 + Use :class:`staticmethod` and :func:`abstractmethod` instead .. function:: 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. - - 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) + .. deprecated:: 3.3 + Use :class:`property` and :func:`abstractmethod` instead .. rubric:: Footnotes diff -r e572a97a1bd1 Doc/whatsnew/3.3.rst --- a/Doc/whatsnew/3.3.rst Fri Jun 10 19:05:16 2011 +0100 +++ b/Doc/whatsnew/3.3.rst Sat Jul 23 12:32:53 2011 -0400 @@ -68,6 +68,20 @@ * Stub +abc +--- + +Improved support for abstract base classes containing descriptors composed with +abstract methods. + + * :class:`abc.abstractproperty` has been deprecated, use :class:`property` + and :func:`abc.abstractmethod` instead. + * :class:`abc.abstractclassmethod` has been deprecated, use + :class:`classmethod` and :func:`abc.abstractmethod` instead. + * :class:`abc.abstractstaticmethod` has been deprecated, use + :class:`property` and :func:`abc.abstractmethod` instead. + + faulthandler ------------ diff -r e572a97a1bd1 Lib/abc.py --- a/Lib/abc.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/abc.py Sat Jul 23 12:32:53 2011 -0400 @@ -20,13 +20,42 @@ @abstractmethod def my_abstract_method(self, ...): ... + @classmethod + @abstractmethod + def my_abstract_classmethod(self, ...): + ... + @staticmethod + @abstractmethod + def my_abstract_method(self, ...): + ... + + @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) """ funcobj.__isabstractmethod__ = True return funcobj class abstractclassmethod(classmethod): - """A decorator indicating abstract classmethods. + """ + .. deprecated:: 3.3 + Use :class:`classmethod` and :func:`abstractmethod` instead. + + A decorator indicating abstract classmethods. Similar to abstractmethod. @@ -41,12 +70,19 @@ __isabstractmethod__ = True def __init__(self, callable): + import warnings + warnings.warn("abstractclassmethod is deprecated", + DeprecationWarning, 2) callable.__isabstractmethod__ = True super().__init__(callable) class abstractstaticmethod(staticmethod): - """A decorator indicating abstract staticmethods. + """ + .. deprecated:: 3.3 + Use :class:`staticmethod` and :func:`abstractmethod` instead. + + A decorator indicating abstract staticmethods. Similar to abstractmethod. @@ -61,12 +97,18 @@ __isabstractmethod__ = True def __init__(self, callable): + import warnings + warnings.warn("abstractstaticmethod is deprecated", + DeprecationWarning, 2) callable.__isabstractmethod__ = True super().__init__(callable) +class abstractproperty(property): + """ + .. deprecated:: 3.3 + Use :class:`property` and :func:`abstractmethod` instead. -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,9 +130,15 @@ def getx(self): ... def setx(self, value): ... x = abstractproperty(getx, setx) + """ __isabstractmethod__ = True + def __init__(self, *args, **kwargs): + import warnings + warnings.warn("abstractproperty is deprecated", DeprecationWarning, 2) + super().__init__(*args, **kwargs) + class ABCMeta(type): diff -r e572a97a1bd1 Lib/numbers.py --- a/Lib/numbers.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/numbers.py Sat Jul 23 12:32:53 2011 -0400 @@ -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 e572a97a1bd1 Lib/test/test_abc.py --- a/Lib/test/test_abc.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/test/test_abc.py Sat Jul 23 12:32:53 2011 -0400 @@ -17,33 +17,38 @@ def foo(self): pass self.assertTrue(foo.__isabstractmethod__) def bar(self): pass - self.assertFalse(hasattr(bar, "__isabstractmethod__")) + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) def test_abstractproperty_basics(self): - @abc.abstractproperty + @property + @abc.abstractmethod def foo(self): pass self.assertTrue(foo.__isabstractmethod__) def bar(self): pass - self.assertFalse(hasattr(bar, "__isabstractmethod__")) + self.assertFalse(getattr(bar, "__isabstractmethod__", False)) class C(metaclass=abc.ABCMeta): - @abc.abstractproperty + @property + @abc.abstractmethod def foo(self): return 3 + self.assertRaises(TypeError, C) class D(C): - @property + @C.foo.getter def foo(self): return super().foo self.assertEqual(D().foo, 3) def test_abstractclassmethod_basics(self): - @abc.abstractclassmethod + @classmethod + @abc.abstractmethod def foo(cls): pass 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 + @classmethod + @abc.abstractmethod def foo(cls): return cls.__name__ self.assertRaises(TypeError, C) class D(C): @@ -53,15 +58,17 @@ self.assertEqual(D().foo(), 'D') def test_abstractstaticmethod_basics(self): - @abc.abstractstaticmethod + @staticmethod + @abc.abstractmethod def foo(): pass 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 + @staticmethod + @abc.abstractmethod def foo(): return 3 self.assertRaises(TypeError, C) class D(C): @@ -71,11 +78,8 @@ 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 + @abc.abstractmethod def foo(self): pass # abstract def bar(self): pass # concrete self.assertEqual(C.__abstractmethods__, {"foo"}) @@ -92,12 +96,71 @@ E() # now foo is concrete, too self.assertFalse(isabstract(E)) class F(E): - @abstractthing + @abc.abstractmethod 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.assertEqual(E.foo.__isabstractmethod__, False) + def test_metaclass_abc(self): # Metaclasses can be ABCs, too. class A(metaclass=abc.ABCMeta): diff -r e572a97a1bd1 Lib/test/test_property.py --- a/Lib/test/test_property.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/test/test_property.py Sat Jul 23 12:32:53 2011 -0400 @@ -128,6 +128,30 @@ 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.assertEqual(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 e572a97a1bd1 Misc/ACKS --- a/Misc/ACKS Fri Jun 10 19:05:16 2011 +0100 +++ b/Misc/ACKS Sat Jul 23 12:32:53 2011 -0400 @@ -209,6 +209,7 @@ Antonio Cuni Brian Curtin Lisandro Dalcin +Darren Dale Andrew Dalke Lars Damerow Evan Dandrea diff -r e572a97a1bd1 Misc/NEWS --- a/Misc/NEWS Fri Jun 10 19:05:16 2011 +0100 +++ b/Misc/NEWS Sat Jul 23 12:32:53 2011 -0400 @@ -10,6 +10,8 @@ Core and Builtins ----------------- +- Issue #11610: Improved support for abstract base classes with descriptors. + - Issue #12265: Make error messages produced by passing an invalid set of arguments to a function more informative. diff -r e572a97a1bd1 Objects/descrobject.c --- a/Objects/descrobject.c Fri Jun 10 19:05:16 2011 +0100 +++ b/Objects/descrobject.c Sat Jul 23 12:32:53 2011 -0400 @@ -1326,6 +1326,63 @@ return 0; } +static int +property_check_isabstract(propertyobject *self, PyObject *obj) +{ + if (obj == NULL) + return 0; + + PyObject* isabstract = PyObject_GetAttrString(obj, "__isabstractmethod__"); + if (isabstract == NULL) { + PyErr_Clear(); + return 0; + } + int res = PyObject_IsTrue(isabstract); + Py_DECREF(isabstract); + return res; +} + +static PyObject * +property_get__isabstractmethod__(PyObject *self, void *closure) +{ + propertyobject *prop = (propertyobject *)self; + + int res = property_check_isabstract(prop, prop->prop_get); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + + res = property_check_isabstract(prop, prop->prop_set); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + + res = property_check_isabstract(prop, prop->prop_del); + if (res == -1) { + return NULL; + } + else if (res) { + Py_RETURN_TRUE; + } + else { + 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" @@ -1391,7 +1448,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 e572a97a1bd1 Objects/funcobject.c --- a/Objects/funcobject.c Fri Jun 10 19:05:16 2011 +0100 +++ b/Objects/funcobject.c Sat Jul 23 12:32:53 2011 -0400 @@ -774,6 +774,28 @@ {NULL} /* Sentinel */ }; +static PyObject * +cm_get__isabstractmethod__(classmethod *cm, void *closure) +{ + if (cm->cm_callable == NULL) + Py_RETURN_FALSE; + PyObject* is_abstract = PyObject_GetAttrString(cm->cm_callable, + "__isabstractmethod__"); + if (is_abstract == NULL) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return is_abstract; +} + +static PyGetSetDef cm_getsetlist[] = { + {"__isabstractmethod__", + (getter)cm_get__isabstractmethod__, NULL, + NULL, + NULL}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(classmethod_doc, "classmethod(function) -> method\n\ \n\ @@ -825,7 +847,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 */ @@ -929,6 +951,28 @@ {NULL} /* Sentinel */ }; +static PyObject * +sm_get__isabstractmethod__(staticmethod *sm, void *closure) +{ + if (sm->sm_callable == NULL) + Py_RETURN_FALSE; + PyObject* is_abstract = PyObject_GetAttrString(sm->sm_callable, + "__isabstractmethod__"); + if (is_abstract == NULL) { + PyErr_Clear(); + Py_RETURN_FALSE; + } + return is_abstract; +} + +static PyGetSetDef sm_getsetlist[] = { + {"__isabstractmethod__", + (getter)sm_get__isabstractmethod__, NULL, + NULL, + NULL}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(staticmethod_doc, "staticmethod(function) -> method\n\ \n\ @@ -977,7 +1021,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 */