我正在尝试编写一个带有泛型的 arg getter,它根据泛型类型输入将结果转换为正确的类型:
T = TypeVar("T")
R = TypeVar("R")
P = ParamSpec("P")
GenericCallable = Callable[P, R]
def get_arg(
name_or_pos: str | int, # 0-based index
typ: type[T],
args: tuple[Any, ...] = (),
kwargs: dict[str, Any] = {},
fallback: T | None = None,
throw: bool = False,
) -> T:
found: Any = None
if isinstance(name_or_pos, str):
name = name_or_pos
found = kwargs.get(name, None)
if found is None:
if throw:
raise RuntimeError(
f"Could not find keyword argument '{name}' of type '{typ!r}' in"
f" {kwargs!r}"
)
found = fallback
elif isinstance(name_or_pos, int):
pos = name_or_pos
if pos >= len(args):
if throw:
raise RuntimeError(
f"Could not find argument at (0-based) pos={pos}, not enough"
f" arg(s) (only have {len(args)}: {', '.join(*args)}"
)
found = fallback # type: ignore
found = args[pos] # type: ignore
if found is None:
found = fallback
return cast(T, found)
给出以下测试用例:
def test_get_arg_for_functions():
def func1(a: str, b: str) -> str:
return f"{a} {b}"
def func2(a: str, b: str, c: str) -> str:
return f"{a} {b} {c}"
class Class1:
def __init__(self, x: int, y: str) -> None:
self.x = x
self.y = y
kwargs = {"func1": func1, "func2": func2, "class1": Class1(1, "a")}
got_func1: Callable[..., str] = get_arg("func1", type(func1), kwargs=kwargs)
assert got_func1("a", "b") == "a b"
got_class1_instance = get_arg("class1", Class1, kwargs=kwargs)
assert got_class1_instance.x == 1
assert got_class1_instance.y == "a"
Func1Type = GenericCallable[[str, str], str]
got_func1_from_type = get_arg("func1", Func1Type, kwargs=kwargs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
assert got_func1_from_type("a", "b") == "a b"
Mypy 抱怨以下内容,对于
got_func1_from_type
:
Argument 2 to "get_arg" has incompatible type "<typing special form>"; expected "type[Never]"
(我相信)这是因为它期待
type[T]
并返回 T
,但这里我传递了一个类型定义。
所以我尝试用
type: T
代替并返回 T
(或者,为了避免混淆,我们称其为 D
:
def get_arg(
...
typ: D,
...
) -> D:
...
现在,Mypy 可以让我将类型定义传递给泛型
typ
:
Func1Type = GenericCallable[[str, str], str]
got_func1_from_type = get_arg_def("func1", Func1Type, kwargs=kwargs)
assert got_func1_from_type("a", "b") == "a b"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
但是
got_func1_from_type
不可调用,因为它是type[(str, str) -> str]
并且Mypy抱怨:
Cannot instantiate type "GenericCallable[(str, str), str]"
这是有道理的,但我不知道该怎么办。我尝试做一些聪明的事情,例如:
D = TypeVar("D", bound=type)
# or
D = TypeVar("D", bound=AnyType)
# or
D = TypeVar("D", bound=type[AnyType])
...但这些都将类型定义绑定到
type[TypeDef]
而不是 actual instance of type D
。
有没有一种方法可以强制转换或声明它返回正确类型的实际实例,同时允许它适用于所有这些用例?
P = ParamSpec("P")
R = TypeVar("R")
GenericCallable = Callable[P, R]
a_str = get_arg("a", str, kwargs=kwargs) # a_str: str
FuncType = GenericCallable[..., str]
a_func = get_arg("func", FuncType, kwargs=kwargs) # a_func: FuncType
an_instance = get_arg("class", MyClass, kwargs=kwargs) # an_instance: MyClass
(P.S.,我用
@overload
尝试了一堆奇特的东西,但无法让它比工会发挥得更好)
有点激进的想法(这可能取决于你的用例) - 但为什么 kwargs 不能只是一个
TypedDict
?我一直在研究各种代码结构,但我不断发现的一件事是,在每次使用 get_arg
时,参数的类型与每个键相关联都是已知的。如果类型已知,为什么我们不能使用更结构化的字典,并且类型检查器可以告诉您是否缺少某个项目?
from typing import Callable, TypedDict
def func1(a: str, b: str) -> str:
return f"{a} {b}"
def func2(a: str, b: str, c: str) -> str:
return f"{a} {b} {c}"
class Class1:
def __init__(self, x: int, y: str) -> None:
self.x = x
self.y = y
class MyDictType(TypedDict):
func1: Callable[[str,str], str]
func2: Callable[[str,str, str], str]
class1: Class1
def test_get_arg_for_functions():
kwargs: MyDictType = {"func1": func1, "func2": func2, "class1": Class1(1, "a")}
got_func1 = kwargs["func1"]
assert got_func1("a", "b") == "a b"
got_class1_instance = kwargs["class1"]
assert got_class1_instance.x == 1
assert got_class1_instance.y == "a"
got_func1_from_type = kwargs["func1"]
assert got_func1_from_type("a", "b") == "a b"
感谢这有点偏离主题,但认为它可能对你仍然有用
看起来类型检查器不喜欢这样:
import typing_extensions as t
F = t.TypeVar("F")
def get(Type: type[F], /) -> F: ...
>>> FuncType = t.Callable[[int, str], bytes]
>>> get(FuncType) # mypy: Argument 1 to "get" has incompatible type "<typing special form>"; expected "type[Never]" [arg-type]
他们对于是否接受回调
typing.Protocol
而不是collections.abc.Callable
也不一致。您可以在类型检查器游乐场中看到一些实验:
我认为 Pyright 的实现更正确,如果你读过 PEP 484 - 类对象的类型:
有时您想讨论类对象,特别是从给定类继承的类对象。这可以拼写为
,其中Type[C]
是一个类。C
FuncType
中的FuncType = t.Callable[[int, str], bytes]
不是一个类,因此它与type[F]
不兼容。然而,typing.Protocol
子类确实是一个类,因此应该可以分配给type[F]
。
我不太同意在
collections.abc.Callable
的地方拒绝打字结构 type[F]
是 有用,尤其是在这种情况下 - 但那是另一个故事了。
一个解决方法(也在这些游乐场中给出)是稍微撒谎并使用
typing.Generic
:
import typing_extensions as t
P = t.ParamSpec("P")
R = t.TypeVar("R", covariant=True)
class GenericCallable(t.Generic[P, R]):
def __call__(self, /, *args: P.args, **kwargs: P.kwargs) -> R: ...
>>> Func1Type = GenericCallable[[str, str], str]
>>> reveal_type(get_arg("func1", Func1Type, kwargs=kwargs)) # mypy: Revealed type is "GenericCallable[[builtins.str, builtins.str], builtins.str]"