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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 17:55:42