python - 如何在保留类型的同时重写类 __init__ 方法

标签 python mypy python-typing

在保持类型注释完整的同时扩展类 __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 可能是我无法完全控制的外部库.

最佳答案

对于“向方法签名添加参数”的问题有一个解决方案 - 但它并不漂亮...... 使用ParamSpecsConcatenate您基本上可以捕获 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/

相关文章:

python - tshark 和 powershell 重定向如何创建字节码文本文件?

python - 如何正确设置 MYPYPATH 以获取 mypy 的 stub ?

python 3.6 : Signature of {method} incompatible with super type {Class}

python - 数据类字段的工厂函数

python - 如何对 pandas、torch 和 numpy 输入使用打字覆盖

python - 使用 numpy.rint() 舍入到最接近的 int 与 0.5 不一致

python - 从 matplotlib 中显示的图中获取数据并将其传递给概率密度函数

Python mypy 类型检查未按预期工作

python - 如何使用 python 类型指定这种可变参数元组?

python - float 到百分比样式错误的 Pandas 数据框列