在保持类型注释完整的同时扩展类 __init__
方法的正确方法是什么?
以这个示例类为例:
class Base:
def __init__(self, *, a: str):
pass
我想对 Base
进行子类化,并向 __init__
方法添加一个新参数 b
:
from typing import Any
class Sub(Base):
def __init__(self, *args: Any, b: str, **kwargs: Any):
super().__init__(*args, **kwargs)
这种方法的问题在于,现在 Sub
基本上接受任何内容。例如,mypy 会很乐意接受以下内容:
Sub(a="", b="", invalid=1). # throws __init__() got an unexpected keyword argument 'invalid'
我也不想在 Sub
中重新定义 a
,因为 Base
可能是我无法完全控制的外部库.
最佳答案
对于“向方法签名添加参数”的问题有一个解决方案 - 但它并不漂亮...... 使用ParamSpecs和 Concatenate您基本上可以捕获 Base init 的参数并扩展它们。
连接只能添加新的位置参数。 PEP 中说明了其原因。介绍 ParamSpec。简而言之,当添加关键字参数时,如果我们正在扩展的函数已使用该关键字参数,我们就会遇到问题。
查看此代码。它非常先进,但这样您就可以保留基类 init 的类型注释而无需重写它们。
from typing import Callable, Type, TypeVar, overload
from typing_extensions import ParamSpec, Concatenate
P = ParamSpec("P")
TSelf = TypeVar("TSelf")
TReturn = TypeVar("TReturn")
T0 = TypeVar("T0")
T1 = TypeVar("T1")
T2 = TypeVar("T2")
@overload
def add_args_to_signature(
to_signature: Callable[Concatenate[TSelf, P], TReturn],
new_arg_type: Type[T0]
) -> Callable[
[Callable[..., TReturn]],
Callable[Concatenate[TSelf, T0, P], TReturn]
]:
pass
@overload
def add_args_to_signature(
to_signature: Callable[Concatenate[TSelf, P], TReturn],
new_arg_type0: Type[T0],
new_arg_type1: Type[T1],
) -> Callable[
[Callable[..., TReturn]],
Callable[Concatenate[TSelf, T0, T1, P], TReturn]
]:
pass
@overload
def add_args_to_signature(
to_signature: Callable[Concatenate[TSelf, P], TReturn],
new_arg_type0: Type[T0],
new_arg_type1: Type[T1],
new_arg_type2: Type[T2]
) -> Callable[
[Callable[..., TReturn]],
Callable[Concatenate[TSelf, T0, T1, P], TReturn]
]:
pass
# repeat if you want to enable adding more parameters...
def add_args_to_signature(
*_, **__
):
return lambda f: f
class Base:
def __init__(self, some_arg: float, *, some_kwarg: int):
pass
class Sub(Base):
# Note: you'll lose the name of your new args in your code editor.
@add_args_to_signature(Base.__init__, str)
def __init__(self, you_can_only_add_positional_args: str, /, *args, **kwargs):
super().__init__(*args, **kwargs)
Sub("hello", 3.5, some_kwarg=5)
VS-Code 为 Sub 提供以下类型提示:Sub(str, some_arg: float, *, some_kwarg: int)
我不知道 mypy 是否可以与 ParamSpec 和 Concatenate 一起使用...
由于 VS-Code 中的错误,参数的位置无法正确匹配(所设置的参数相差 1)。
请注意,这是打字模块的高级用法。 如果您需要更多解释,可以在评论中联系我。
关于python - 如何在保留类型的同时重写类 __init__ 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75070946/