From e21200bb0930a8b14773a4b33eda77b02138e91b Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Mon, 28 Feb 2022 23:54:01 -0600 Subject: [PATCH 1/7] Allow Protocol classes to define __init__ --- Lib/test/test_typing.py | 20 ++++++++++++++++++++ Lib/typing.py | 3 ++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index dc1514d63b7775..3f40532e66a207 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1074,6 +1074,26 @@ class CG(PG[T]): pass with self.assertRaises(TypeError): CG[int](42) + def test_protocol_defining_init(self): + class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x + + # the protocol itself cannot be instantiated + with self.assertRaises(TypeError): + P() + + # but a concrete subclass can + class C(P): pass + + c = C(1) + self.assertIsInstance(c, C) + + # and this concrete subclass can inherit the __init__ + # implementation from Protocol + 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 ad1435ed23d272..06c2818daba1ef 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1741,7 +1741,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): From fb419d11759de518f1fe477b52fdb6ac5f2a34ce Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Wed, 2 Mar 2022 04:25:59 +0000 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst 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 00000000000000..2d62391b5df2fd --- /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 __init__ methods defined on subclasses From 1fe020aa4636ddfb466d072b21041407af0341e5 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:29:18 -0600 Subject: [PATCH 3/7] split up and improve test --- Lib/test/test_typing.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3f40532e66a207..7286c1afe444c2 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1074,24 +1074,31 @@ class CG(PG[T]): pass with self.assertRaises(TypeError): CG[int](42) - def test_protocol_defining_init(self): + def test_protocol_defining_init_does_not_get_overriden(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 - # the protocol itself cannot be instantiated - with self.assertRaises(TypeError): - P() + c = C() + P.__init__(c, 1) + self.assertEqual(c.x, 1) + + def test_concrete_class_inherting_init_from_protocol(self): + class P(Protocol): + x: int + def __init__(self, x: int) -> None: + self.x = x - # but a concrete subclass can class C(P): pass c = C(1) self.assertIsInstance(c, C) - - # and this concrete subclass can inherit the __init__ - # implementation from Protocol self.assertEqual(c.x, 1) def test_cannot_instantiate_abstract(self): From 9c22a4404952fee29455611f44e35130797d0a0b Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:38:50 -0600 Subject: [PATCH 4/7] Update Lib/test/test_typing.py Co-authored-by: Jelle Zijlstra --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7286c1afe444c2..29958152275950 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1074,7 +1074,7 @@ class CG(PG[T]): pass with self.assertRaises(TypeError): CG[int](42) - def test_protocol_defining_init_does_not_get_overriden(self): + def test_protocol_defining_init_does_not_get_overridden(self): # check that P.__init__ doesn't get clobbered # see https://bugs.python.org/issue44807 From 282e95f7ae342272d6c0b5729dfdd8ea7668069e Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:38:58 -0600 Subject: [PATCH 5/7] Update Misc/NEWS.d/next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst Co-authored-by: Jelle Zijlstra --- .../next/Library/2022-03-02-04-25-58.bpo-44807.gHNC9J.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 2d62391b5df2fd..4757d3420caf8a 100644 --- 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 @@ -1 +1 @@ -:class:`typing.Protocol` no longer silently replaces __init__ methods defined on subclasses +:class:`typing.Protocol` no longer silently replaces :meth:`__init__` methods defined on subclasses. Patch by Adrian Garcia Badaracco. From 28cf1967bf34435e93654b0172dc14e6230b42e9 Mon Sep 17 00:00:00 2001 From: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com> Date: Tue, 1 Mar 2022 22:39:03 -0600 Subject: [PATCH 6/7] Update Lib/test/test_typing.py Co-authored-by: Jelle Zijlstra --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 29958152275950..9da596588d3617 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1089,7 +1089,7 @@ class C: pass P.__init__(c, 1) self.assertEqual(c.x, 1) - def test_concrete_class_inherting_init_from_protocol(self): + def test_concrete_class_inheriting_init_from_protocol(self): class P(Protocol): x: int def __init__(self, x: int) -> None: From e2840af2ac381a4a45bcf89d3b462ef3d29770df Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 9 Mar 2022 20:58:49 -0800 Subject: [PATCH 7/7] Update Lib/test/test_typing.py --- Lib/test/test_typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 9da596588d3617..bf23eb6bdcb090 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1082,7 +1082,6 @@ class P(Protocol): x: int def __init__(self, x: int) -> None: self.x = x - class C: pass c = C()