diff options
author | Benjamin Peterson <benjamin@python.org> | 2011-12-15 20:34:02 (GMT) |
---|---|---|
committer | Benjamin Peterson <benjamin@python.org> | 2011-12-15 20:34:02 (GMT) |
commit | bfebb7b54a50f01104f7b6169de77f7fc8feb912 (patch) | |
tree | ef88caf96404fbb445d25a695eaa8e51bc750566 /Lib | |
parent | a8ff01ca7422117dcd906ee2ea55c5293eeceb24 (diff) | |
download | cpython-bfebb7b54a50f01104f7b6169de77f7fc8feb912.zip cpython-bfebb7b54a50f01104f7b6169de77f7fc8feb912.tar.gz cpython-bfebb7b54a50f01104f7b6169de77f7fc8feb912.tar.bz2 |
improve abstract property support (closes #11610)
Thanks to Darren Dale for patch.
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/abc.py | 19 | ||||
-rw-r--r-- | Lib/numbers.py | 14 | ||||
-rw-r--r-- | Lib/test/test_abc.py | 172 | ||||
-rw-r--r-- | Lib/test/test_property.py | 23 |
4 files changed, 210 insertions, 18 deletions
@@ -26,7 +26,8 @@ def abstractmethod(funcobj): class abstractclassmethod(classmethod): - """A decorator indicating abstract classmethods. + """ + A decorator indicating abstract classmethods. Similar to abstractmethod. @@ -36,6 +37,9 @@ class abstractclassmethod(classmethod): @abstractclassmethod def my_abstract_classmethod(cls, ...): ... + + 'abstractclassmethod' is deprecated. Use 'classmethod' with + 'abstractmethod' instead. """ __isabstractmethod__ = True @@ -46,7 +50,8 @@ class abstractclassmethod(classmethod): class abstractstaticmethod(staticmethod): - """A decorator indicating abstract staticmethods. + """ + A decorator indicating abstract staticmethods. Similar to abstractmethod. @@ -56,6 +61,9 @@ class abstractstaticmethod(staticmethod): @abstractstaticmethod def my_abstract_staticmethod(...): ... + + 'abstractstaticmethod' is deprecated. Use 'staticmethod' with + 'abstractmethod' instead. """ __isabstractmethod__ = True @@ -66,7 +74,8 @@ class abstractstaticmethod(staticmethod): 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 @@ class abstractproperty(property): def getx(self): ... def setx(self, value): ... x = abstractproperty(getx, setx) + + 'abstractproperty' is deprecated. Use 'property' with 'abstractmethod' + instead. """ + __isabstractmethod__ = True diff --git a/Lib/numbers.py b/Lib/numbers.py index ecfad7c..b206457 100644 --- a/Lib/numbers.py +++ b/Lib/numbers.py @@ -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 @@ class Complex(Number): """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 @@ class Complex(Number): """ raise NotImplementedError - @abstractproperty + @property + @abstractmethod def imag(self): """Retrieve the imaginary component of this number. @@ -272,11 +274,13 @@ class Rational(Real): __slots__ = () - @abstractproperty + @property + @abstractmethod def numerator(self): raise NotImplementedError - @abstractproperty + @property + @abstractmethod def denominator(self): raise NotImplementedError diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 1319a64..653c957 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -10,14 +10,7 @@ import abc 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 TestABC(unittest.TestCase): 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 @@ class TestABC(unittest.TestCase): 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 @@ class TestABC(unittest.TestCase): 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 @@ class TestABC(unittest.TestCase): 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 --git a/Lib/test/test_property.py b/Lib/test/test_property.py index cc6a872..726d6fe 100644 --- a/Lib/test/test_property.py +++ b/Lib/test/test_property.py @@ -128,6 +128,29 @@ class PropertyTests(unittest.TestCase): 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): |