From 1d967be5ea5a79c04344986953a3dca37c68fcbd Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Thu, 24 Sep 2026 20:09:40 +0300 Subject: [PATCH 1/3] gh-83888: Document how an abstract property is made concrete (abc) ABCMeta only inspects the object 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, so a read-write abstract property can be overridden by a read-only one without the subclass losing its instantiability, and the missing setter surfaces only as an AttributeError when the attribute is assigned to. Document that in the abstractmethod section, and cancel the implicature that was reported in the deprecated abstractproperty section by saying outright that borrowing the remaining components from the base class is not the only way to override an abstract property. --- Doc/library/abc.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Doc/library/abc.rst b/Doc/library/abc.rst index be25a94e7e94f6..24741ab177ed95 100644 --- a/Doc/library/abc.rst +++ b/Doc/library/abc.rst @@ -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 @@ -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: From 86ddcefee4c303e0650be5db447fdc787e419506 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Thu, 24 Sep 2026 20:25:18 +0300 Subject: [PATCH 2/3] gh-83888: Test that a concrete override may drop abstract components (abc) test_descriptors_with_abstractmethod only covers overrides built with property.getter and property.setter, which carry the abstract components of the base property forward. Nothing pinned the behaviour documented in the previous commit: that binding an abstract name to any non-abstract object -- a read-only property, or a plain value -- makes it concrete, whatever the original descriptor provided. --- Lib/test/test_abc.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Lib/test/test_abc.py b/Lib/test/test_abc.py index 814d7fff2f4135..87c946f2c452b0 100644 --- a/Lib/test/test_abc.py +++ b/Lib/test/test_abc.py @@ -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): From 68fbeb7e7fa53b3e5f30bf0a1b800effbbd385c4 Mon Sep 17 00:00:00 2001 From: Dmitry Voropaev Date: Fri, 25 Sep 2026 14:08:12 +0300 Subject: [PATCH 3/3] gh-83888: Add a news entry --- .../2026-09-25-13-10-00.gh-issue-83888.Rt4nQv.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Documentation/2026-09-25-13-10-00.gh-issue-83888.Rt4nQv.rst diff --git a/Misc/NEWS.d/next/Documentation/2026-09-25-13-10-00.gh-issue-83888.Rt4nQv.rst b/Misc/NEWS.d/next/Documentation/2026-09-25-13-10-00.gh-issue-83888.Rt4nQv.rst new file mode 100644 index 00000000000000..5c45f4ba5ea521 --- /dev/null +++ b/Misc/NEWS.d/next/Documentation/2026-09-25-13-10-00.gh-issue-83888.Rt4nQv.rst @@ -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.