Skip to content

Commit 0f6a773

Browse files
bpo-44806: Fix __init__ in subclasses of protocols (GH-27545) (GH-27559)
Non-protocol subclasses of protocol ignore now the __init__ method inherited from protocol base classes. (cherry picked from commit 043cd60) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 4817c14 commit 0f6a773

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

Lib/test/test_typing.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,9 @@ class P(Protocol): pass
745745
class C(P): pass
746746

747747
self.assertIsInstance(C(), C)
748+
with self.assertRaises(TypeError):
749+
C(42)
750+
748751
T = TypeVar('T')
749752

750753
class PG(Protocol[T]): pass
@@ -759,6 +762,8 @@ class PG(Protocol[T]): pass
759762
class CG(PG[T]): pass
760763

761764
self.assertIsInstance(CG[int](), CG)
765+
with self.assertRaises(TypeError):
766+
CG[int](42)
762767

763768
def test_cannot_instantiate_abstract(self):
764769
@runtime_checkable
@@ -1194,6 +1199,37 @@ def __init__(self):
11941199

11951200
self.assertEqual(C[int]().test, 'OK')
11961201

1202+
class B:
1203+
def __init__(self):
1204+
self.test = 'OK'
1205+
1206+
class D1(B, P[T]):
1207+
pass
1208+
1209+
self.assertEqual(D1[int]().test, 'OK')
1210+
1211+
class D2(P[T], B):
1212+
pass
1213+
1214+
self.assertEqual(D2[int]().test, 'OK')
1215+
1216+
def test_new_called(self):
1217+
T = TypeVar('T')
1218+
1219+
class P(Protocol[T]): pass
1220+
1221+
class C(P[T]):
1222+
def __new__(cls, *args):
1223+
self = super().__new__(cls, *args)
1224+
self.test = 'OK'
1225+
return self
1226+
1227+
self.assertEqual(C[int]().test, 'OK')
1228+
with self.assertRaises(TypeError):
1229+
C[int](42)
1230+
with self.assertRaises(TypeError):
1231+
C[int](a=42)
1232+
11971233
def test_protocols_bad_subscripts(self):
11981234
T = TypeVar('T')
11991235
S = TypeVar('S')

Lib/typing.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,8 +1080,7 @@ def _is_callable_members_only(cls):
10801080

10811081

10821082
def _no_init(self, *args, **kwargs):
1083-
if type(self)._is_protocol:
1084-
raise TypeError('Protocols cannot be instantiated')
1083+
raise TypeError('Protocols cannot be instantiated')
10851084

10861085

10871086
def _allow_reckless_class_cheks():
@@ -1210,6 +1209,15 @@ def _proto_hook(other):
12101209

12111210
# We have nothing more to do for non-protocols...
12121211
if not cls._is_protocol:
1212+
if cls.__init__ == _no_init:
1213+
for base in cls.__mro__:
1214+
init = base.__dict__.get('__init__', _no_init)
1215+
if init != _no_init:
1216+
cls.__init__ = init
1217+
break
1218+
else:
1219+
# should not happen
1220+
cls.__init__ = object.__init__
12131221
return
12141222

12151223
# ... otherwise check consistency of bases, and prohibit instantiation.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Non-protocol subclasses of :class:`typing.Protocol` ignore now the
2+
``__init__`` method inherited from protocol base classes.

0 commit comments

Comments
 (0)