]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-138010: Fix `__init_subclass__` forwarding by `warnings.deprecated` (#138210)
authorBrian Schubert <brianm.schubert@gmail.com>
Fri, 5 Sep 2025 20:44:50 +0000 (16:44 -0400)
committerGitHub <noreply@github.com>
Fri, 5 Sep 2025 20:44:50 +0000 (20:44 +0000)
Lib/_py_warnings.py
Lib/test/test_warnings/__init__.py
Misc/NEWS.d/next/Library/2025-08-27-17-05-36.gh-issue-138010.ZZJmPL.rst [new file with mode: 0644]

index 5070caea6bb05488507afcd15277b4fbe4b65770..2e7113a637a2ac16ffb59e2ce937e24989a0e5db 100644 (file)
@@ -765,27 +765,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):
                     _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
index 694cfc97064c30a1e83efac9985c0db0e0e87400..256713365286c2ca5709e764c3a8678f15fc4b20 100644 (file)
@@ -1863,6 +1863,25 @@ class DeprecatedTests(PyPublicAPITests):
 
         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 (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.