Skip to content

Commit cf62144

Browse files
[3.13] pythongh-138010: Fix __init_subclass__ forwarding by warnings.deprecated (pythonGH-138210) (python#138564)
(cherry picked from commit e2c038f)
1 parent 4e25f01 commit cf62144

File tree

3 files changed

+36
-13
lines changed

3 files changed

+36
-13
lines changed

Lib/test/test_warnings/__init__.py

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

16841684
self.assertEqual(D.inited, 3)
16851685

1686+
def test_existing_init_subclass_in_sibling_base(self):
1687+
@deprecated("A will go away soon")
1688+
class A:
1689+
pass
1690+
class B:
1691+
def __init_subclass__(cls, x):
1692+
super().__init_subclass__()
1693+
cls.inited = x
1694+
1695+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1696+
class C(A, B, x=42):
1697+
pass
1698+
self.assertEqual(C.inited, 42)
1699+
1700+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1701+
class D(B, A, x=42):
1702+
pass
1703+
self.assertEqual(D.inited, 42)
1704+
16861705
def test_init_subclass_has_correct_cls(self):
16871706
init_subclass_saw = None
16881707

Lib/warnings.py

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

603603
arg.__new__ = staticmethod(__new__)
604604

605-
original_init_subclass = arg.__init_subclass__
606-
# We need slightly different behavior if __init_subclass__
607-
# is a bound method (likely if it was implemented in Python)
608-
if isinstance(original_init_subclass, MethodType):
609-
original_init_subclass = original_init_subclass.__func__
605+
if "__init_subclass__" in arg.__dict__:
606+
# __init_subclass__ is directly present on the decorated class.
607+
# Synthesize a wrapper that calls this method directly.
608+
original_init_subclass = arg.__init_subclass__
609+
# We need slightly different behavior if __init_subclass__
610+
# is a bound method (likely if it was implemented in Python).
611+
# Otherwise, it likely means it's a builtin such as
612+
# object's implementation of __init_subclass__.
613+
if isinstance(original_init_subclass, MethodType):
614+
original_init_subclass = original_init_subclass.__func__
610615

611616
@functools.wraps(original_init_subclass)
612617
def __init_subclass__(*args, **kwargs):
613618
warn(msg, category=category, stacklevel=stacklevel + 1)
614619
return original_init_subclass(*args, **kwargs)
615-
616-
arg.__init_subclass__ = classmethod(__init_subclass__)
617-
# Or otherwise, which likely means it's a builtin such as
618-
# object's implementation of __init_subclass__.
619620
else:
620-
@functools.wraps(original_init_subclass)
621-
def __init_subclass__(*args, **kwargs):
621+
def __init_subclass__(cls, *args, **kwargs):
622622
warn(msg, category=category, stacklevel=stacklevel + 1)
623-
return original_init_subclass(*args, **kwargs)
623+
return super(arg, cls).__init_subclass__(*args, **kwargs)
624624

625-
arg.__init_subclass__ = __init_subclass__
625+
arg.__init_subclass__ = classmethod(__init_subclass__)
626626

627627
arg.__deprecated__ = __new__.__deprecated__ = msg
628628
__init_subclass__.__deprecated__ = msg
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)