[Python-checkins] bpo-44807: Allow Protocol classes to define __init__ (GH-31628)

JelleZijlstra webhook-mailer at python.org
Mon Apr 11 10:51:37 EDT 2022


https://github.com/python/cpython/commit/5f2abae61ec69264b835dcabe2cdabe57b9a990e
commit: 5f2abae61ec69264b835dcabe2cdabe57b9a990e
branch: main
author: Adrian Garcia Badaracco <1755071+adriangb at users.noreply.github.com>
committer: JelleZijlstra <jelle.zijlstra at gmail.com>
date: 2022-04-11T07:51:25-07:00
summary:

bpo-44807: Allow Protocol classes to define __init__ (GH-31628)

Co-authored-by: Jelle Zijlstra <jelle.zijlstra at gmail.com>

files:
A Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst
M Lib/test/test_typing.py
M Lib/typing.py

diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index e09f8aa3fb849..b884f7b2cced3 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -1598,6 +1598,32 @@ class CG(PG[T]): pass
         with self.assertRaises(TypeError):
             CG[int](42)
 
+    def test_protocol_defining_init_does_not_get_overridden(self):
+        # check that P.__init__ doesn't get clobbered
+        # see https://bugs.python.org/issue44807
+
+        class P(Protocol):
+            x: int
+            def __init__(self, x: int) -> None:
+                self.x = x
+        class C: pass
+
+        c = C()
+        P.__init__(c, 1)
+        self.assertEqual(c.x, 1)
+
+    def test_concrete_class_inheriting_init_from_protocol(self):
+        class P(Protocol):
+            x: int
+            def __init__(self, x: int) -> None:
+                self.x = x
+
+        class C(P): pass
+
+        c = C(1)
+        self.assertIsInstance(c, C)
+        self.assertEqual(c.x, 1)
+
     def test_cannot_instantiate_abstract(self):
         @runtime_checkable
         class P(Protocol):
diff --git a/Lib/typing.py b/Lib/typing.py
index 26c6b8c278b73..ec8cbbd8b20a3 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1997,7 +1997,8 @@ def _proto_hook(other):
                     issubclass(base, Generic) and base._is_protocol):
                 raise TypeError('Protocols can only inherit from other'
                                 ' protocols, got %r' % base)
-        cls.__init__ = _no_init_or_replace_init
+        if cls.__init__ is Protocol.__init__:
+            cls.__init__ = _no_init_or_replace_init
 
 
 class _AnnotatedAlias(_GenericAlias, _root=True):
diff --git a/Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst b/Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst
new file mode 100644
index 0000000000000..4757d3420caf8
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst
@@ -0,0 +1 @@
+:class:`typing.Protocol` no longer silently replaces :meth:`__init__` methods defined on subclasses. Patch by Adrian Garcia Badaracco.



More information about the Python-checkins mailing list