mirror of
https://github.com/python/cpython.git
synced 2025-07-29 06:05:00 +00:00
bpo-45081: Fix __init__ method generation when inheriting from Protocol (GH-28121) (GH-28132)
Co-authored-by: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com>
(cherry picked from commit 0635e201be
)
Co-authored-by: Yurii Karabas <1998uriyyo@gmail.com>
This commit is contained in:
parent
ca27109c17
commit
98eb40828a
3 changed files with 47 additions and 13 deletions
|
@ -9,7 +9,7 @@ import inspect
|
||||||
import builtins
|
import builtins
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
|
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional, Protocol
|
||||||
from typing import get_type_hints
|
from typing import get_type_hints
|
||||||
from collections import deque, OrderedDict, namedtuple
|
from collections import deque, OrderedDict, namedtuple
|
||||||
from functools import total_ordering
|
from functools import total_ordering
|
||||||
|
@ -2124,6 +2124,26 @@ class TestInit(unittest.TestCase):
|
||||||
self.x = 2 * x
|
self.x = 2 * x
|
||||||
self.assertEqual(C(5).x, 10)
|
self.assertEqual(C(5).x, 10)
|
||||||
|
|
||||||
|
def test_inherit_from_protocol(self):
|
||||||
|
# Dataclasses inheriting from protocol should preserve their own `__init__`.
|
||||||
|
# See bpo-45081.
|
||||||
|
|
||||||
|
class P(Protocol):
|
||||||
|
a: int
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C(P):
|
||||||
|
a: int
|
||||||
|
|
||||||
|
self.assertEqual(C(5).a, 5)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class D(P):
|
||||||
|
def __init__(self, a):
|
||||||
|
self.a = a * 2
|
||||||
|
|
||||||
|
self.assertEqual(D(5).a, 10)
|
||||||
|
|
||||||
|
|
||||||
class TestRepr(unittest.TestCase):
|
class TestRepr(unittest.TestCase):
|
||||||
def test_repr(self):
|
def test_repr(self):
|
||||||
|
|
|
@ -1079,8 +1079,29 @@ def _is_callable_members_only(cls):
|
||||||
return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
|
return all(callable(getattr(cls, attr, None)) for attr in _get_protocol_attrs(cls))
|
||||||
|
|
||||||
|
|
||||||
def _no_init(self, *args, **kwargs):
|
def _no_init_or_replace_init(self, *args, **kwargs):
|
||||||
raise TypeError('Protocols cannot be instantiated')
|
cls = type(self)
|
||||||
|
|
||||||
|
if cls._is_protocol:
|
||||||
|
raise TypeError('Protocols cannot be instantiated')
|
||||||
|
|
||||||
|
# Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`.
|
||||||
|
# The first instantiation of the subclass will call `_no_init_or_replace_init` which
|
||||||
|
# searches for a proper new `__init__` in the MRO. The new `__init__`
|
||||||
|
# replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent
|
||||||
|
# instantiation of the protocol subclass will thus use the new
|
||||||
|
# `__init__` and no longer call `_no_init_or_replace_init`.
|
||||||
|
for base in cls.__mro__:
|
||||||
|
init = base.__dict__.get('__init__', _no_init_or_replace_init)
|
||||||
|
if init is not _no_init_or_replace_init:
|
||||||
|
cls.__init__ = init
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# should not happen
|
||||||
|
cls.__init__ = object.__init__
|
||||||
|
|
||||||
|
cls.__init__(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def _allow_reckless_class_cheks():
|
def _allow_reckless_class_cheks():
|
||||||
|
@ -1209,15 +1230,6 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
||||||
|
|
||||||
# We have nothing more to do for non-protocols...
|
# We have nothing more to do for non-protocols...
|
||||||
if not cls._is_protocol:
|
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
|
return
|
||||||
|
|
||||||
# ... otherwise check consistency of bases, and prohibit instantiation.
|
# ... otherwise check consistency of bases, and prohibit instantiation.
|
||||||
|
@ -1228,7 +1240,7 @@ class Protocol(Generic, metaclass=_ProtocolMeta):
|
||||||
issubclass(base, Generic) and base._is_protocol):
|
issubclass(base, Generic) and base._is_protocol):
|
||||||
raise TypeError('Protocols can only inherit from other'
|
raise TypeError('Protocols can only inherit from other'
|
||||||
' protocols, got %r' % base)
|
' protocols, got %r' % base)
|
||||||
cls.__init__ = _no_init
|
cls.__init__ = _no_init_or_replace_init
|
||||||
|
|
||||||
|
|
||||||
class _AnnotatedAlias(_GenericAlias, _root=True):
|
class _AnnotatedAlias(_GenericAlias, _root=True):
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix issue when dataclasses that inherit from ``typing.Protocol`` subclasses
|
||||||
|
have wrong ``__init__``. Patch provided by Yurii Karabas.
|
Loading…
Add table
Add a link
Reference in a new issue