You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

子类显式实现Protocol触发'Protocols cannot be instantiated'错误原因

Python 3.9中Protocol与super()的冲突问题

问题代码

from typing import Protocol

class Base:
    pass

class MyProto(Protocol):
    def hello(self) -> None:
        pass

class Child(Base, MyProto):
    def __init__(self) -> None:
        super().__init__()  # 报错位置

    def hello(self) -> None:
        return print("hello")

Child()

报错信息

TypeError: Protocols cannot be instantiated

疑问点

我清楚Protocol不能被实例化,但疑惑Python为什么没自动规避这个错误?有没有其他显式实现Protocol的方式?目前知道直接调用Base.__init__(self)能正常运行,但更习惯用super(),感觉这像是语言的设计疏漏。


问题根源

这是Python方法解析顺序(MRO)在处理Protocol时的特殊逻辑导致的:
当你调用super().__init__()时,Python会按MRO顺序遍历父类查找__init__方法。虽然MyProto是Protocol,但它在Child的继承列表里,会被纳入MRO查找链。而Protocol本身没有合法的__init__实现(因为它禁止实例化),所以当super遍历到它时就触发了错误。

Python没自动规避这个问题,是因为在3.9版本中,Protocol在运行时本质还是普通类(直到3.11才对其运行时行为做了更严格的限制),语法层面没阻止它被放入继承列表参与MRO流程。

解决方法

  1. 调整继承顺序
    把Protocol放到继承列表的最后,让MRO优先找到Base的__init__:
class Child(MyProto, Base):  # 调换父类顺序
    def __init__(self) -> None:
        super().__init__()

    def hello(self) -> None:
        print("hello")

不过这种方式依赖MRO顺序,稳定性一般。

  1. 给Protocol加runtime_checkable装饰器
    这个装饰器会让Protocol在运行时更严格地扮演协议角色,不会被当作普通父类参与MRO方法查找:
from typing import Protocol, runtime_checkable

@runtime_checkable
class MyProto(Protocol):
    def hello(self) -> None:
        pass

class Child(Base, MyProto):
    def __init__(self) -> None:
        super().__init__()

    def hello(self) -> None:
        print("hello")

这样super()会正确定位到Base的__init__,不会触发错误。

  1. 显式指定super的查找起始点
    给super传入参数,明确跳过Protocol所在的查找链:
class Child(Base, MyProto):
    def __init__(self) -> None:
        super(Base, self).__init__()  # 明确从Base之后开始查找,跳过MyProto

    def hello(self) -> None:
        print("hello")

关于显式实现Protocol的正确姿势

Python的Protocol采用结构子类型,不需要显式继承(除非需要运行时检查)。只要你的类实现了协议要求的方法,就自动符合协议规范:

class Child(Base):
    def hello(self) -> None:
        print("hello")

# 类型检查器会认为Child符合MyProto协议
def func(p: MyProto) -> None:
    p.hello()

func(Child())  # 类型检查通过,运行正常

只有当你需要运行时验证类是否符合协议时,才需要显式继承并配合runtime_checkable装饰器。

内容的提问来源于stack exchange,提问作者Noam-N

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 03:32:34