]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-138010: Fix `__init_subclass__` forwarding by `warnings.deprecated` (GH...
authorBrian Schubert <brianm.schubert@gmail.com>
Fri, 5 Sep 2025 21:21:19 +0000 (17:21 -0400)
committerGitHub <noreply@github.com>
Fri, 5 Sep 2025 21:21:19 +0000 (14:21 -0700)
(cherry picked from commit e2c038f5be2df39a7b06364dec0836a25c423b67)

Lib/test/test_warnings/__init__.py
Lib/warnings.py
Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst [new file with mode: 0644]

index a1c9bd67cdcd9037d94fa5da926be34e52881876..57016d5a292cd94024ea77bc16d690b2f8ecdcfb 100644 (file)
@@ -1683,6 +1683,25 @@ class DeprecatedTests(unittest.TestCase):
 
         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
 
index f83aaf231eaa55bb4b99f438154cbb59548a744e..04320d47679439be2909a0c005a9c5111066d135 100644 (file)
@@ -602,27 +602,27 @@ class deprecated:
 
             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):
                     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):
                     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/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 (file)
index 0000000..117f3ad
--- /dev/null
@@ -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.