Python中如何为Protocol指定特定构造函数签名?
如何定义要求特定构造函数的Protocol类型?
你遇到的问题是因为Protocol默认描述的是实例的接口,而你试图用它约束类的构造行为——Constructible被定义为实例类型,但你传入的是类本身,且__init__是实例方法,无法用来描述类的可调用性,所以mypy报错“Constructible not callable”。
下面是几种可行的解决方案:
方案一:用Type[T]约束类类型与实例接口
先定义描述实例行为的Protocol,再用Type[T]表示该实例对应的类类型,这样就能约束类的构造函数签名:
from typing import Protocol, Iterable, TypeVar, Type T = TypeVar('T', bound='ConstructibleInstance') class ConstructibleInstance(Protocol): def get_value(self) -> int: ... def map_is(cs: Iterable[Type[T]], i: int) -> tuple[tuple[T, ...], tuple[int, ...]]: mapped = tuple(C(i) for C in cs) values = tuple(c.get_value() for c in mapped) return mapped, values # 实现类 class X: def __init__(self, i: int): self.i = i def get_value(self) -> int: return self.i class Sq: def __init__(self, i: int): self.i = i def get_value(self) -> int: return self.i * self.i cs, values = map_is((X, Sq), 5) assert values == (5, 25)
Type[T]会告诉mypy:传入的对象是T类型的类,必须能通过__init__(i: int)构造出T实例,同时实例要满足ConstructibleInstance的接口要求。
方案二:直接定义类类型的Protocol
如果想明确在Protocol中写出构造函数的要求,可以把Protocol与Type[T]结合,直接约束类的__init__签名:
from typing import Protocol, Iterable, TypeVar T = TypeVar('T', bound='Constructible') class Constructible(Protocol): def __init__(self, i: int): ... def get_value(self) -> int: ... def map_is(cs: Iterable[Type[T]], i: int) -> tuple[tuple[T, ...], tuple[int, ...]]: mapped = tuple(C(i) for C in cs) values = tuple(c.get_value() for c in mapped) return mapped, values class X: def __init__(self, i: int): self.i = i def get_value(self) -> int: return self.i class Sq: def __init__(self, i: int): self.i = i def get_value(self) -> int: return self.i * self.i cs, values = map_is((X, Sq), 5) assert values == (5, 25)
这里的Type[T]同样起到了关联类与实例的作用,mypy会自动检查类的__init__是否符合Protocol中的定义。
方案三:改用工厂类方法(你提到的替代方案)
如果不想直接依赖__init__的签名,可以在Protocol中定义一个工厂类方法,统一构造入口,这种方式更灵活,也更符合面向接口编程的思想:
from typing import Protocol, Iterable, TypeVar, Self T = TypeVar('T', bound='ConstructibleInstance') class ConstructibleFactory(Protocol): @classmethod def construct(cls, i: int) -> T: ... class ConstructibleInstance(Protocol): def get_value(self) -> int: ... def map_is(cs: Iterable[ConstructibleFactory[T]], i: int) -> tuple[tuple[T, ...], tuple[int, ...]]: mapped = tuple(C.construct(i) for C in cs) values = tuple(c.get_value() for c in mapped) return mapped, values class X: def __init__(self, i: int): self.i = i @classmethod def construct(cls, i: int) -> Self: return cls(i) def get_value(self) -> int: return self.i class Sq: def __init__(self, i: int): self.i = i @classmethod def construct(cls, i: int) -> Self: return cls(i) def get_value(self) -> int: return self.i * self.i cs, values = map_is((X, Sq), 5) assert values == (5, 25)
这种方式把构造逻辑封装成类方法,后续如果需要修改构造逻辑,只需要修改construct方法,不需要改动调用方代码。
内容的提问来源于stack exchange,提问作者xtofl
相关产品推荐
相关产品推荐

