Skip to content

Commit 51dfc67

Browse files
[3.14] gh-138010: Fix __init_subclass__ forwarding by warnings.deprecated (GH-138210) (#138561)
gh-138010: Fix `__init_subclass__` forwarding by `warnings.deprecated` (GH-138210) (cherry picked from commit e2c038f) Co-authored-by: Brian Schubert <brianm.schubert@g
1 parent e104aad commit 51dfc67

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
@@ -762,27 +762,27 @@ def __new__(cls, /, *args, **kwargs):
762762

763763
arg.__new__ = staticmethod(__new__)
764764

765-
original_init_subclass = arg.__init_subclass__
766-
# We need slightly different behavior if __init_subclass__
767-
# is a bound method (likely if it was implemented in Python)
768-
if isinstance(original_init_subclass, MethodType):
769-
original_init_subclass = original_init_subclass.__func__
765+
if "__init_subclass__" in arg.__dict__:
766+
# __init_subclass__ is directly present on the decorated class.
767+
# Synthesize a wrapper that calls this method directly.
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+
# Otherwise, it likely means it's a builtin such as
772+
# object's implementation of __init_subclass__.
773+
if isinstance(original_init_subclass, MethodType):
774+
original_init_subclass = original_init_subclass.__func__
770775

771776
@functools.wraps(original_init_subclass)
772777
def __init_subclass__(*args, **kwargs):
773778
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
774779
return original_init_subclass(*args, **kwargs)
775-
776-
arg.__init_subclass__ = classmethod(__init_subclass__)
777-
# Or otherwise, which likely means it's a builtin such as
778-
# object's implementation of __init_subclass__.
779780
else:
780-
@functools.wraps(original_init_subclass)
781-
def __init_subclass__(*args, **kwargs):
781+
def __init_subclass__(cls, *args, **kwargs):
782782
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
783-
return original_init_subclass(*args, **kwargs)
783+
return super(arg, cls).__init_subclass__(*args, **kwargs)
784784

785-
arg.__init_subclass__ = __init_subclass__
785+
arg.__init_subclass__ = classmethod(__init_subclass__)
786786

787787
arg.__deprecated__ = __new__.__deprecated__ = msg
788788
__init_subclass__.__deprecated__ = msg

Lib/test/test_warnings/__init__.py

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

18701870
self.assertEqual(D.inited, 3)
18711871

1872+
def test_existing_init_subclass_in_sibling_base(self):
1873+
@deprecated("A will go away soon")
1874+
class A:
1875+
pass
1876+
class B:
1877+
def __init_subclass__(cls, x):
1878+
super().__init_subclass__()
1879+
cls.inited = x
1880+
1881+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1882+
class C(A, B, x=42):
1883+
pass
1884+
self.assertEqual(C.inited, 42)
1885+
1886+
with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
1887+
class D(B, A, x=42):
1888+
pass
1889+
self.assertEqual(D.inited, 42)
1890+
18721891
def test_init_subclass_has_correct_cls(self):
18731892
init_subclass_saw = None
18741893

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)