考虑下面的代码:
from typing import Protocol, ClassVar, Any
class Tool(Protocol):
t: ClassVar
# def f(self, params: self.t) -> Any:
# NameError: name 'self' is not defined
def f(self, params) -> Any:
pass
class Sum:
t: ClassVar = list[int]
def f(self, params: list[int]) -> int:
return sum(params)
def use_tool(tool: Tool, params: Any) -> Any:
return tool.f(params)
s = Sum()
print(use_tool(s, [1, 2]))
我想定义一个协议
Tool
,这样像
Sum
这样的类可以用特定的
t
来实现它,而在
Sum.f()
等方法中,
params
会受到限制在类型检查中具有
t
的类型,它存储类型/类对象。我该怎么做?
我试图在类型注释中使用
self.t
来引用
t
,但这不起作用,用
self
替换
typing.Self
也不起作用。
我认为存储类型对于
f()
的参数在假设
t
存储 a
pydantic.BaseModel
(这意味着
t
的类型为
ModelMetaclass
)的情况下很有用,那么在像
use_tool
这样的函数中,我可以查看
t
via
t.model_dump_json()
的模式。此外,
use_tool()
可以将 JSON 字符串作为输入,并在调用
t.model_validate_json()
之前通过
Tool.f
将其解析为适当的模型。
你可以使用
TypeVar
和泛型来实现你想要的。 以下是如何操作:
from typing import Protocol, TypeVar, Generic, Any
T = TypeVar('T')
class Tool(Protocol, Generic[T]):
t: type[T]
def f(self, params: T) -> Any:
...
class Sum:
t = list[int]
def f(self, params: list[int]) -> int:
return sum(params)
def use_tool(tool: Tool[Any], params: Any) -> Any:
return tool.f(params)
s = Sum()
print(use_tool(s, [1, 2]))
解释:
-
TypeVar
: 我们首先定义一个TypeVar
T
,它表示一个可以是任何类型的占位符。 -
Generic[T]
: 我们使Tool
协议成为一个泛型协议,它接受一个类型参数T
。这意味着Tool
可以被任何具体的类型实例化,例如Tool[list[int]]
或Tool[str]
。 -
t: type[T]
: 我们将t
声明为一个类变量,其类型为type[T]
。这表示t
将存储一个类型,而该类型必须是T
的子类。 -
f(self, params: T) -> Any
: 在f
方法中,我们使用T
作为params
参数的类型提示。 这意味着传递给f
的参数类型必须与t
存储的类型匹配。
通过这种方式,你可以确保传递给
f
方法的参数类型与存储在
t
中的类型一致,同时仍然允许不同的类以不同的类型实现
Tool
协议。