
本文介绍解决 Pyre 报错 Invalid class instantiation [45] 的核心方案:用 Protocol 替代 type[ABC] 作为工厂方法返回类型,使类型检查器理解返回值是可调用的、具象类的构造器,而非抽象类本身。
本文介绍解决 pyre 报错 `invalid class instantiation [45]` 的核心方案:用 `protocol` 替代 `type[abc]` 作为工厂方法返回类型,使类型检查器理解返回值是可调用的、具象类的构造器,而非抽象类本身。
Pyre 对抽象类(ABC)的实例化检查比 mypy 和 Pyright 更严格——它不仅在运行时检测 ABC 继承关系,还会将任何含 @abstractmethod(包括 __init__)的类视为不可实例化的抽象类。因此,当你写 def get_class() -> type[MyClassABC]: 并随后调用 factory_cls(name="test") 时,Pyre 会误判 factory_cls 是 MyClassABC 类型本身(即抽象类),从而触发错误。
根本问题在于:type[MyClassABC] 在 Pyre 的语义中允许指向抽象类本身,而你实际需要的是“一个能构造 MyClassABC 实例的具体类的构造器”,即一个具备特定签名的可调用对象。
✅ 正确解法是使用 typing.Protocol 定义一个结构化协议,精准描述构造函数的调用行为:
from abc import abstractmethod
from typing import Protocol, Dict, Type, Any
class MyClassABC:
@abstractmethod
def __init__(self, name: str) -> None:
pass
# ✅ 关键:定义构造器协议,而非 type[MyClassABC]
class MyClassConstructor(Protocol):
def __call__(self, name: str) -> MyClassABC: ...
class MyClass(MyClassABC):
def __init__(self, name: str) -> None:
self.name = name
class MyClass2(MyClassABC):
def __init__(self, name: str) -> None:
self.name = name
class ClassFactory:
# 类型注解明确:映射值是构造器(Protocol)或具体类(type[...])
MAPPING: Dict[str, MyClassConstructor | Type[MyClassABC]] = {
"myclass": MyClass,
"myclass2": MyClass2,
}
@staticmethod
def get_class() -> MyClassConstructor:
return ClassFactory.MAPPING["myclass"]
# ✅ 安全调用:Pyre 理解 factory_cls 是可调用且返回 MyClassABC 实例
if __name__ == "__main__":
factory_cls = ClassFactory.get_class()
instance = factory_cls(name="test") # ✅ 无 Pyre 错误
reveal_type(instance) # Revealed type is "MyClassABC"
? 为什么这样有效?
-
MyClassConstructor协议不继承自MyClassABC,也不包含抽象方法;它仅声明__call__行为,因此 Pyre 不会将其关联到抽象类实例化约束。 -
MyClass和MyClass2自动满足该协议(duck typing),因为它们的__init__签名匹配__call__声明。 - 返回类型
MyClassConstructor明确告诉类型检查器:“这个值是用来调用的,不是用来isinstance(..., MyClassABC)的”。
⚠️ 注意事项:
- 协议中的
__call__签名必须与所有目标类的__init__完全一致(参数名、类型、顺序、默认值)。若子类构造函数不同(如MyClass3.__init__(self, name: str, age: int)),需扩展协议或拆分协议。 - 不要移除
@abstractmethod—— 它对 IDE 参数提示和 mypy/pyright 兼容性至关重要;本方案与之完全兼容。 - 若需支持多参数或可选参数,协议应如实反映,例如:
class MyClassConstructor(Protocol): def __call__(self, name: str, age: int = ...) -> MyClassABC: ...
总结:面对 Pyre 对 type[ABC] 的严苛限制,放弃“类型容器”思路,转向“行为契约”(Protocol)是最佳实践。它既保持了类型安全与 IDE 支持,又彻底规避了抽象类实例化误报,是现代 Python 工厂模式类型提示的推荐范式。










