Skip to content

Commit 1fd478d

Browse files
brianschubertlkollar
authored andcommitted
pythongh-138010: Fix __init_subclass__ forwarding by warnings.deprecated (python#138210)
1 parent 872cb17 commit 1fd478d

File tree

3 files changed

+36
-13
lines changed

3 files changed

+36
-13
lines changed

Lib/_py_warnings.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -765,27 +765,27 @@ def __new__(cls, /, *args, **kwargs):
765765

766766
arg.__new__ = staticmethod(__new__)
767767

768-
original_init_subclass = arg.__init_subclass__
769-
# We need slightly different behavior if __init_subclass__
770-
# is a bound method (likely if it was implemented in Python)
771-
if isinstance(original_init_subclass, MethodType):
772-
original_init_subclass = original_init_subclass.__func__
768+
if "__init_subclass__" in arg.__dict__:
769+
# __init_subclass__ is directly present on the decorated class.
770+
# Synthesize a wrapper that calls this method directly.
771+
original_init_subclass = arg.__init_subclass__
772+
# We need slightly different behavior if __init_subclass__
773+
# is a bound method (likely if it was implemented in Python).
774+
# Otherwise, it likely means it's a builtin such as
775+
# object's implementation of __init_subclass__.
776+
if isinstance(original_init_subclass, MethodType):
777+
original_init_subclass = original_init_subclass.__func__
773778

774779
@functools.wraps(original_init_subclass)
775780
def __init_subclass__(*args, **kwargs):
776781
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
777782
return original_init_subclass(*args, **kwargs)
778-
779-
arg.__init_subclass__ = classmethod(__init_subclass__)
780-
# Or otherwise, which likely means it's a builtin such as
781-
# object's implementation of __init_subclass__.
782783
else:
783-
@functools.wraps(original_init_subclass)
784-
def __init_subclass__(*args, **kwargs):
784+
def __init_subclass__(cls, *args, **kwargs):
785785
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
786-
return original_init_subclass(*args, **kwargs)
786+
return super(arg, cls).__init_subclass__(*args, **kwargs)
787787

788-
arg.__init_subclass__ = __init_subclass__
788+
arg.__init_subclass__ = classmethod(__init_subclass__)
789789

790790
arg.__deprecated__ = __new__.__deprecated__ = msg
791791
__init_subclass__.__deprecated__ = msg

Lib/test/test_warnings/__init__.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1863,6 +1863,25 @@ class D(C, x=3):
18631863

18641864
self.assertEqual(D.inited, 3)
18651865

1866+
def test_existing_init_subclass_in_sibling_base(self):
1867+
@deprecated("A will go away soon")
1868+
class A:
1869+
pass
1870+
class B:
1871+
def __init_subclass__(cls, x):
1872+
super().__init_subclass__()
1873+
cls.inited = x
1874+
1875+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1876+
class C(A, B, x=42):
1877+
pass
1878+
self.assertEqual(C.inited, 42)
1879+
1880+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1881+
class D(B, A, x=42):
1882+
pass
1883+
self.assertEqual(D.inited, 42)
1884+
18661885
def test_init_subclass_has_correct_cls(self):
18671886
init_subclass_saw = None
18681887

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix an issue where defining a class with an :func:`@warnings.deprecated
2+
<warnings.deprecated>`-decorated base class may not invoke the correct
3+
:meth:`~object.__init_subclass__` method in cases involving multiple
4+
inheritance. Patch by Brian Schubert.

0 commit comments

Comments
 (0)