typing.Protocol class `__init__` 方法在显式子类型构造期间未调用

typing.Protocol class `__init__` method not called during explicit subtype construction

Python 的 PEP 544 为结构子类型引入了 typing.Protocol,a.k.a。 “静态鸭子打字”。

在本 PEP 关于 Merging and extending protocols 的部分中,指出

The general philosophy is that protocols are mostly like regular ABCs, but a static type checker will handle them specially.

因此,人们期望从 typing.Protocol 的子class 继承的方式与人们期望从 [=17= 的子class 继承的方式大致相同]:

from abc import ABC
from typing import Protocol

class AbstractBase(ABC):
    def method(self):
        print("AbstractBase.method called")

class Concrete1(AbstractBase):
    ...

c1 = Concrete1()
c1.method()  # prints "AbstractBase.method called"

class ProtocolBase(Protocol):
    def method(self):
        print("ProtocolBase.method called")

class Concrete2(ProtocolBase):
    ...

c2 = Concrete2()
c2.method()  # prints "ProtocolBase.method called"

正如预期的那样,具体的子classes Concrete1Concrete2 从它们各自的superclasses 继承了method。此行为记录在 PEP 的 Explicitly declaring implementation 部分:

To explicitly declare that a certain class implements a given protocol, it can be used as a regular base class. In this case a class could use default implementations of protocol members.

...

Note that there is little difference between explicit and implicit subtypes, the main benefit of explicit subclassing is to get some protocol methods "for free".

但是,当协议class实现了__init__方法时,__init__被显式subclass继承的协议的 es class。这与 ABC class 的子 class 形成对比,后者 do 继承 __init__ 方法:

from abc import ABC
from typing import Protocol

class AbstractBase(ABC):
    def __init__(self):
        print("AbstractBase.__init__ called")

class Concrete1(AbstractBase):
    ...

c1 = Concrete1()  # prints "AbstractBase.__init__ called"

class ProtocolBase(Protocol):
    def __init__(self):
        print("ProtocolBase.__init__ called")

class Concrete2(ProtocolBase):
    ...

c2 = Concrete2()  # NOTHING GETS PRINTED

我们看到,Concrete1继承了AbstractBase__init__,但是Concrete2没有继承ProtocolBase__init__。这与前面的示例形成对比,其中 Concrete1Concrete2 都从各自的 superclasses.

继承 method

我的问题是:

  1. 不让 __init__ 被协议 class 的显式子类型继承的基本原理是什么?协议 class 是否有某种类型理论原因无法“免费”提供 __init__ 方法?
  2. 是否有任何关于此差异的文档?或者这是一个错误?

您不能直接实例化协议 class。这是 currently implemented 通过将协议的 __init__ 替换为唯一功能是强制执行此限制的方法:

def _no_init(self, *args, **kwargs):
    if type(self)._is_protocol:
        raise TypeError('Protocols cannot be instantiated')

...

class Protocol(Generic, metaclass=_ProtocolMeta):
    ...

    def __init_subclass__(cls, *args, **kwargs):
        ...
        cls.__init__ = _no_init

你的 __init__ 没有执行,因为它已经不存在了。

这很奇怪,而且比乍一看更混乱 - 例如,它与多重继承的交互很差,中断了 super().__init__ 链。