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 043cd60abe)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2021-08-02 09:52:16 -07:00 committed by GitHub
parent 4817c14395
commit 0f6a7739df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 48 additions and 2 deletions

View file

@ -745,6 +745,9 @@ class ProtocolTests(BaseTestCase):
class C(P): pass
self.assertIsInstance(C(), C)
with self.assertRaises(TypeError):
C(42)
T = TypeVar('T')
class PG(Protocol[T]): pass
@ -759,6 +762,8 @@ class ProtocolTests(BaseTestCase):
class CG(PG[T]): pass
self.assertIsInstance(CG[int](), CG)
with self.assertRaises(TypeError):
CG[int](42)
def test_cannot_instantiate_abstract(self):
@runtime_checkable
@ -1194,6 +1199,37 @@ class ProtocolTests(BaseTestCase):
self.assertEqual(C[int]().test, 'OK')
class B:
def __init__(self):
self.test = 'OK'
class D1(B, P[T]):
pass
self.assertEqual(D1[int]().test, 'OK')
class D2(P[T], B):
pass
self.assertEqual(D2[int]().test, 'OK')
def test_new_called(self):
T = TypeVar('T')
class P(Protocol[T]): pass
class C(P[T]):
def __new__(cls, *args):
self = super().__new__(cls, *args)
self.test = 'OK'
return self
self.assertEqual(C[int]().test, 'OK')
with self.assertRaises(TypeError):
C[int](42)
with self.assertRaises(TypeError):
C[int](a=42)
def test_protocols_bad_subscripts(self):
T = TypeVar('T')
S = TypeVar('S')

View file

@ -1080,8 +1080,7 @@ def _is_callable_members_only(cls):
def _no_init(self, *args, **kwargs):
if type(self)._is_protocol:
raise TypeError('Protocols cannot be instantiated')
raise TypeError('Protocols cannot be instantiated')
def _allow_reckless_class_cheks():
@ -1210,6 +1209,15 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
# We have nothing more to do for non-protocols...
if not cls._is_protocol:
if cls.__init__ == _no_init:
for base in cls.__mro__:
init = base.__dict__.get('__init__', _no_init)
if init != _no_init:
cls.__init__ = init
break
else:
# should not happen
cls.__init__ = object.__init__
return
# ... otherwise check consistency of bases, and prohibit instantiation.

View file

@ -0,0 +1,2 @@
Non-protocol subclasses of :class:`typing.Protocol` ignore now the
``__init__`` method inherited from protocol base classes.