Skip to content

Commit 2cc19a5

Browse files
bpo-44806: Fix __init__ in subclasses of protocols (GH-27545)
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 <[email protected]>
1 parent 9de5901 commit 2cc19a5

File tree

3 files changed

+48
-2
lines changed

3 files changed

+48
-2
lines changed

Diff for: Lib/test/test_typing.py

+36
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,9 @@ class P(Protocol): pass
875875
class C(P): pass
876876

877877
self.assertIsInstance(C(), C)
878+
with self.assertRaises(TypeError):
879+
C(42)
880+
878881
T = TypeVar('T')
879882

880883
class PG(Protocol[T]): pass
@@ -889,6 +892,8 @@ class PG(Protocol[T]): pass
889892
class CG(PG[T]): pass
890893

891894
self.assertIsInstance(CG[int](), CG)
895+
with self.assertRaises(TypeError):
896+
CG[int](42)
892897

893898
def test_cannot_instantiate_abstract(self):
894899
@runtime_checkable
@@ -1316,6 +1321,37 @@ def __init__(self):
13161321

13171322
self.assertEqual(C[int]().test, 'OK')
13181323

1324+
class B:
1325+
def __init__(self):
1326+
self.test = 'OK'
1327+
1328+
class D1(B, P[T]):
1329+
pass
1330+
1331+
self.assertEqual(D1[int]().test, 'OK')
1332+
1333+
class D2(P[T], B):
1334+
pass
1335+
1336+
self.assertEqual(D2[int]().test, 'OK')
1337+
1338+
def test_new_called(self):
1339+
T = TypeVar('T')
1340+
1341+
class P(Protocol[T]): pass
1342+
1343+
class C(P[T]):
1344+
def __new__(cls, *args):
1345+
self = super().__new__(cls, *args)
1346+
self.test = 'OK'
1347+
return self
1348+
1349+
self.assertEqual(C[int]().test, 'OK')
1350+
with self.assertRaises(TypeError):
1351+
C[int](42)
1352+
with self.assertRaises(TypeError):
1353+
C[int](a=42)
1354+
13191355
def test_protocols_bad_subscripts(self):
13201356
T = TypeVar('T')
13211357
S = TypeVar('S')

Diff for: Lib/typing.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -1379,8 +1379,7 @@ def _is_callable_members_only(cls):
13791379

13801380

13811381
def _no_init(self, *args, **kwargs):
1382-
if type(self)._is_protocol:
1383-
raise TypeError('Protocols cannot be instantiated')
1382+
raise TypeError('Protocols cannot be instantiated')
13841383

13851384
def _caller(depth=1, default='__main__'):
13861385
try:
@@ -1523,6 +1522,15 @@ def _proto_hook(other):
15231522

15241523
# We have nothing more to do for non-protocols...
15251524
if not cls._is_protocol:
1525+
if cls.__init__ == _no_init:
1526+
for base in cls.__mro__:
1527+
init = base.__dict__.get('__init__', _no_init)
1528+
if init != _no_init:
1529+
cls.__init__ = init
1530+
break
1531+
else:
1532+
# should not happen
1533+
cls.__init__ = object.__init__
15261534
return
15271535

15281536
# ... otherwise check consistency of bases, and prohibit instantiation.
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)