diff -r 3d5e0d123838 Doc/library/abc.rst --- a/Doc/library/abc.rst Tue Mar 22 18:34:21 2011 +0100 +++ b/Doc/library/abc.rst Thu Mar 24 10:18:31 2011 -0400 @@ -194,31 +194,102 @@ .. versionadded:: 3.2 -.. function:: abstractproperty(fget=None, fset=None, fdel=None, doc=None) +.. class:: 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:: + class A(metaclass=ABCMeta): + def getx(self): ... + x = abstractproperty(getx) + + However, this example does not make a lot of sense, because the + abstractproperty has been passed a concrete method:: + + class B(A): + x = property(A.getx) + + To improve the situation, methods that should be overridden with concrete + implementations should be decorated with abstractmethod before they are + passed to abstractproperty:: + class C(metaclass=ABCMeta): + @abstractmethod + def getx(self): + ... + @abstractmethod + def setx(self, value): + ... + x = abstractproperty(getx, setx) + + You can also define an abstract property using the decorator form of + property declaration:: + + class D(metaclass=ABCMeta): @abstractproperty - def my_abstract_property(self): + @abstractmethod + def x(self): + ... + @x.setter + @abstractmethod + def x(self, val): ... - This defines a read-only property; you can also define a read-write abstract - property using the 'long' form of property declaration:: + Subclasses will not be instantiable until the property definition is fully + concrete:: - class C(metaclass=ABCMeta): - def getx(self): ... - def setx(self, value): ... - x = abstractproperty(getx, setx) + class E(D): + @D.x.getter + def x(self): + ... + + class F(D): + @D.x.getter + def x(self): + ... + @x.setter + def x(self, val): + ... + + In this example, 'E' is not instantiable, because the 'x' setter is + abstract. 'F' has provided concrete implementations for all of the abstract + methods associated with 'x', so 'x' is now a regular property , and 'F' is + instantiable. + + .. method:: getter(fget) + + Descriptor to change the getter on a property. Calls + :meth:`abstractproperty.create_property`. + + .. method:: setter(fset) + + Descriptor to change the setter on a property. Calls + :meth:`abstractproperty.create_property`. + + .. method:: getter(fdel) + + Descriptor to change the deleter on a property. Calls + :meth:`abstractproperty.create_property`. + + .. classmethod:: create_property(fget=None, fset=None, fdel=None, doc=None) + + A factory function that returns a new instance of the class if any of + 'fget', 'fset', or 'fdel' are abstract methods, otherwise it returns a + concrete property. This method is called by + :func:`abstractproperty.getter`, :func:`abstractproperty.setter` and + :func:`abstractproperty.deleter`. + + .. versionadded:: 3.3 + Supports the decorator form of property declaration. + + Added :meth:`abstractproperty.create_property` class method. .. rubric:: Footnotes diff -r 3d5e0d123838 Lib/abc.py --- a/Lib/abc.py Tue Mar 22 18:34:21 2011 +0100 +++ b/Lib/abc.py Thu Mar 24 10:18:31 2011 -0400 @@ -76,21 +76,104 @@ Usage: + class A(metaclass=ABCMeta): + def getx(self): ... + x = abstractproperty(getx) + + However, this example does not make a lot of sense, because + the abstractproperty has been passed a concrete method: + + class B(A): + x = property(A.getx) + + To improve the situation, methods that should be overridden + with concrete implementations should be decorated with + abstractmethod before they are passed to abstractproperty: + class C(metaclass=ABCMeta): + @abstractmethod + def getx(self): + ... + @abstractmethod + def setx(self, value): + ... + x = abstractproperty(getx, setx) + + You can also define an abstract property using the decorator form + of property declaration: + + class D(metaclass=ABCMeta): @abstractproperty - def my_abstract_property(self): + @abstractmethod + def x(self): + ... + @x.setter + @abstractmethod + def x(self, val): ... - This defines a read-only property; you can also define a read-write - abstract property using the 'long' form of property declaration: + Subclasses will not be instantiable until the property definition + is fully concrete: - class C(metaclass=ABCMeta): - def getx(self): ... - def setx(self, value): ... - x = abstractproperty(getx, setx) + class E(D): + @D.x.getter + def x(self): + ... + + class F(D): + @D.x.getter + def x(self): + ... + @x.setter + def x(self, val): + ... + + In this example, 'E' is not instantiable, because the 'x' setter + is abstract. 'F' has provided concrete implementations for all of + the abstract methods associated with 'x', so 'x' is now a regular + property , and 'F' is instantiable. """ + __isabstractmethod__ = True + def getter(self, fun): + """Descriptor to change the getter on a property + + This method calls the create_property class method. + """ + return self.create_property(fun, self.fset, self.fdel, self.__doc__) + + def setter(self, fun): + """Descriptor to change the setter on a property + + This method calls the create_property class method. + """ + return self.create_property(self.fget, fun, self.fdel, self.__doc__) + + def deleter(self, fun): + """Descriptor to change the deleter on a property + + This method calls the create_property class method. + """ + return self.create_property(self.fget, self.fset, fun, self.__doc__) + + @classmethod + def create_property(cls, fget=None, fset=None, fdel=None, doc=None): + """Create a new property + + 'create_property' is a factory function that returns a new + instance of 'cls' if any of 'fget', 'fset', or 'fdel' are + abstract methods, otherwise it returns a concrete property. + + This method is called by 'getter', 'setter', and 'deleter'. + """ + for f in (fget, fset, fdel): + if (f is not None) and getattr(f, '__isabstractmethod__', False): + # has abstract methods, return an abstract property + return cls(fget, fset, fdel, doc) + # does not have abstract methods, return a concrete property + return property(fget, fset, fdel, doc) + class ABCMeta(type): diff -r 3d5e0d123838 Lib/test/test_abc.py --- a/Lib/test/test_abc.py Tue Mar 22 18:34:21 2011 +0100 +++ b/Lib/test/test_abc.py Thu Mar 24 10:18:31 2011 -0400 @@ -29,11 +29,33 @@ 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) + def test_abstractproperty_with_subclassing(self): + class C(metaclass=abc.ABCMeta): + @abc.abstractproperty + @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) + self.assertTrue(isinstance(D.foo, abc.abstractproperty)) + class E(D): + @D.foo.setter + def foo(self, val): pass + self.assertEqual(E().foo, 3) + self.assertFalse(isinstance(E.foo, abc.abstractproperty)) + self.assertTrue(isinstance(E.foo, property)) + def test_abstractclassmethod_basics(self): @abc.abstractclassmethod def foo(cls): pass