Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions Doc/library/abc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,16 @@ The :mod:`!abc` module also provides the following decorator:
return any(getattr(f, '__isabstractmethod__', False) for
f in (self._fget, self._fset, self._fdel))

:class:`ABCMeta` only inspects the object that a subclass finally binds to
an abstract name: if that object does not report itself as abstract, the
name counts as implemented. Nothing checks that a descriptor replacing an
abstract one still provides the same components. Overriding the read-write
``my_abstract_property`` above with a read-only :deco:`property`, for
example, does not prevent the subclass from being instantiated, and the
missing setter is reported only as an :exc:`AttributeError` when the
attribute is assigned to. A :term:`static type checker` can flag such an
override; the abstract base class machinery does not.

.. note::

Unlike Java abstract methods, these abstract
Expand Down Expand Up @@ -318,6 +328,12 @@ The :mod:`!abc` module also supports the following legacy decorators:
def x(self, val):
...

This borrows the remaining components from ``C.x``, but it is not the only
way to override an abstract property. As described for
:deco:`abstractmethod` above, binding the name to a new, non-abstract
:deco:`property` also makes the attribute concrete, even if every component
of the original was abstract and the replacement provides fewer of them.


The :mod:`!abc` module also provides the following functions:

Expand Down
29 changes: 29 additions & 0 deletions Lib/test/test_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,35 @@ class E(D):
def foo(self, val): pass
self.assertFalse(E.foo.__isabstractmethod__)

def test_concrete_override_drops_abstract_components(self):
# Only the object a subclass finally binds to an abstract name is
# inspected; nothing checks that it still provides the components
# of the descriptor it replaces. See gh-83888.
class C(metaclass=abc_ABCMeta):
@property
@abc.abstractmethod
def foo(self): ...
@foo.setter
@abc.abstractmethod
def foo(self, val): ...
self.assertEqual(C.__abstractmethods__, {"foo"})
# A read-only property makes the read-write abstract property
# concrete, silently dropping the setter.
class D(C):
@property
def foo(self): return 3
self.assertEqual(D.__abstractmethods__, set())
d = D()
self.assertEqual(d.foo, 3)
with self.assertRaises(AttributeError):
d.foo = 4
# The same holds for an abstract name that is not a descriptor at
# all: any non-abstract object counts as an implementation.
class E(C):
foo = 3
self.assertEqual(E.__abstractmethods__, set())
self.assertEqual(E().foo, 3)

def test_metaclass_abc(self):
# Metaclasses can be ABCs, too.
class A(metaclass=abc_ABCMeta):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Document that :class:`~abc.ABCMeta` only inspects the object a subclass binds
to an abstract name, so overriding an abstract read-write property with a
read-only one leaves the class instantiable.
Loading