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 Fri Jun 10 17:38:08 2011 -0400 @@ -133,13 +133,12 @@ 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 @@ -154,6 +153,24 @@ 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:: Unlike Java abstract methods, these abstract @@ -196,29 +213,8 @@ .. 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 Fri Jun 10 17:38:08 2011 -0400 @@ -68,6 +68,16 @@ * 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. + + faulthandler ------------ diff -r e572a97a1bd1 Lib/abc.py --- a/Lib/abc.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/abc.py Fri Jun 10 17:38:08 2011 -0400 @@ -11,8 +11,9 @@ 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 normal - 'super' call mechanisms. + abstractmethod can be used to specify abstract methods for + properties and descriptors. The abstract methods can be called + using any of the normal 'super' call mechanisms. Usage: @@ -20,6 +21,24 @@ @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 @@ -66,7 +85,11 @@ class abstractproperty(property): - """A decorator indicating abstract properties. + """ + .. deprecated:: 3.3 + Use :class:`property` and :func:`abstractmethod` instead. + + 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 +111,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): @@ -115,16 +144,40 @@ def __new__(mcls, name, bases, namespace): cls = super().__new__(mcls, name, bases, namespace) + # Compute set of abstract method names - abstracts = {name - for name, value in namespace.items() - if getattr(value, "__isabstractmethod__", False)} + def is_descriptor(value): + return (hasattr(value, '__get__') or hasattr(value, '__set__') + or hasattr(value, '__delete__')) + def is_abstract(value): + return getattr(value, "__isabstractmethod__", False) + def get_abstract_names_for_item(item): + name, value = item + if is_abstract(value): + return [name] + elif is_descriptor(value): + return ['{}.{}'.format(name, attr) for attr in dir(value) + if is_abstract(getattr(value, attr))] + return [] + + abstract_names = [] + for item in namespace.items(): + abstract_names.extend(get_abstract_names_for_item(item)) + 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__ = frozenset(abstracts) + for name in getattr(base, "__abstractmethods__", ()): + descr_name, is_descr, attr = name.rpartition('.') + if is_descr: + # base class identified a descriptor abstract method: + descr = getattr(cls, descr_name, None) + val = getattr(descr, attr, None) + else: + val = getattr(cls, name, None) + if val is None or is_abstract(val): + abstract_names.append(name) + + cls.__abstractmethods__ = frozenset(abstract_names) + # Set up inheritance registry cls._abc_registry = WeakSet() cls._abc_cache = WeakSet() diff -r e572a97a1bd1 Lib/numbers.py --- a/Lib/numbers.py Fri Jun 10 19:05:16 2011 +0100 +++ b/Lib/numbers.py Fri Jun 10 17:38:08 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 Fri Jun 10 17:38:08 2011 -0400 @@ -20,6 +20,7 @@ self.assertFalse(hasattr(bar, "__isabstractmethod__")) def test_abstractproperty_basics(self): + "abstractproperty is deprecated in python-3.3" @abc.abstractproperty def foo(self): pass self.assertTrue(foo.__isabstractmethod__) @@ -29,6 +30,7 @@ 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 @@ -98,6 +100,47 @@ 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) + class F(C): + @property + def foo(self): return 3 + self.assertRaises(TypeError, F) + + def test_pathologically_named_descriptors(self): + """Test that attribute names containing "." doesn't choke ABCMeta""" + class MyProperty(property): + pass + class C(metaclass=abc.ABCMeta): + @MyProperty + @abc.abstractmethod + def foo(self): return 1 + setattr(foo, 'pathological.desc_method', None) + @MyProperty + def bar(self): return + setattr(bar, 'pathological.desc_method', None) + setattr(self, 'pathological.bar', foo) + self.assertRaises(TypeError, C) + class D(C): + @C.foo.getter + def foo(self): return 2 + self.assertEqual(D().foo, 2) + def test_metaclass_abc(self): # Metaclasses can be ABCs, too. class A(metaclass=abc.ABCMeta): diff -r e572a97a1bd1 Misc/NEWS --- a/Misc/NEWS Fri Jun 10 19:05:16 2011 +0100 +++ b/Misc/NEWS Fri Jun 10 17:38:08 2011 -0400 @@ -187,6 +187,8 @@ Library ------- +- Issue #11610: Improved support for abstract base classes with descriptors. + - Issue #12009: Fixed regression in netrc file comment handling. - Issue #12246: Warn and fail when trying to install a third-party project from