https://github.com/python/cpython/commit/51dfc67092475e6772485aac8394c342771f96af
commit: 51dfc67092475e6772485aac8394c342771f96af
branch: 3.14
author: Miss Islington (bot) <[email protected]>
committer: JelleZijlstra <[email protected]>
date: 2025-10-07T21:29:28-07:00
summary:
[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 e2c038f5be2df39a7b06364dec0836a25c423b67)
Co-authored-by: Brian Schubert <brianm.schubert@g
files:
A Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst
M Lib/_py_warnings.py
M Lib/test/test_warnings/__init__.py
diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py
index cbaa94458629ac..c3583fe81ad9b0 100644
--- a/Lib/_py_warnings.py
+++ b/Lib/_py_warnings.py
@@ -762,27 +762,27 @@ def __new__(cls, /, *args, **kwargs):
arg.__new__ = staticmethod(__new__)
- original_init_subclass = arg.__init_subclass__
- # We need slightly different behavior if __init_subclass__
- # is a bound method (likely if it was implemented in Python)
- if isinstance(original_init_subclass, MethodType):
- original_init_subclass = original_init_subclass.__func__
+ if "__init_subclass__" in arg.__dict__:
+ # __init_subclass__ is directly present on the decorated class.
+ # Synthesize a wrapper that calls this method directly.
+ original_init_subclass = arg.__init_subclass__
+ # We need slightly different behavior if __init_subclass__
+ # is a bound method (likely if it was implemented in Python).
+ # Otherwise, it likely means it's a builtin such as
+ # object's implementation of __init_subclass__.
+ if isinstance(original_init_subclass, MethodType):
+ original_init_subclass = original_init_subclass.__func__
@functools.wraps(original_init_subclass)
def __init_subclass__(*args, **kwargs):
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
return original_init_subclass(*args, **kwargs)
-
- arg.__init_subclass__ = classmethod(__init_subclass__)
- # Or otherwise, which likely means it's a builtin such as
- # object's implementation of __init_subclass__.
else:
- @functools.wraps(original_init_subclass)
- def __init_subclass__(*args, **kwargs):
+ def __init_subclass__(cls, *args, **kwargs):
_wm.warn(msg, category=category, stacklevel=stacklevel + 1)
- return original_init_subclass(*args, **kwargs)
+ return super(arg, cls).__init_subclass__(*args, **kwargs)
- arg.__init_subclass__ = __init_subclass__
+ arg.__init_subclass__ = classmethod(__init_subclass__)
arg.__deprecated__ = __new__.__deprecated__ = msg
__init_subclass__.__deprecated__ = msg
diff --git a/Lib/test/test_warnings/__init__.py
b/Lib/test/test_warnings/__init__.py
index f89e94449b3031..f495f0fcb0f97f 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -1869,6 +1869,25 @@ class D(C, x=3):
self.assertEqual(D.inited, 3)
+ def test_existing_init_subclass_in_sibling_base(self):
+ @deprecated("A will go away soon")
+ class A:
+ pass
+ class B:
+ def __init_subclass__(cls, x):
+ super().__init_subclass__()
+ cls.inited = x
+
+ with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
+ class C(A, B, x=42):
+ pass
+ self.assertEqual(C.inited, 42)
+
+ with self.assertWarnsRegex(DeprecationWarning, "A will go away soon"):
+ class D(B, A, x=42):
+ pass
+ self.assertEqual(D.inited, 42)
+
def test_init_subclass_has_correct_cls(self):
init_subclass_saw = None
diff --git
a/Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst
b/Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst
new file mode 100644
index 00000000000000..117f3ad6c6761b
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst
@@ -0,0 +1,4 @@
+Fix an issue where defining a class with an :func:`@warnings.deprecated
+<warnings.deprecated>`-decorated base class may not invoke the correct
+:meth:`~object.__init_subclass__` method in cases involving multiple
+inheritance. Patch by Brian Schubert.
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]