子类从父类继承函数的注解。

4
class A:
    def test(self, value: int, *args: str, **kwargs: int) -> str:
        pass 


class B(A)
    def test(self, value, *args, **kwargs):
        # do some stuff
        super().test(value)

有没有办法告诉mypy子类的test与父类具有相同的类型?

我问这个问题是因为我继承的一些方法的类型需要很多导入。一个例子是requests.Session.get。如果我只是编写一个包装器,在将所有内容发送到正确的函数之前操纵标头,如何告诉mypy仅考虑特定函数的父类注释?


1
B.test 放在一个 if not typing.TYPE_CHECKING 块下。 - dROOOze
在“做一些事情”的代码中,实际上是否利用了任何参数? - blhsing
@blhsing 是的,它使用了大部分(但不是全部)的参数。关键是我希望测试对IDE透明,以便它显示与底层函数相同的注释。 - Some Guy
1
这实际上是一个优先级为 1 的所需功能。这是相关问题(https://github.com/python/mypy/issues/3903)(太长不看 - 不,你不能)。 - SUTerliakov
真糟糕,感到十分遗憾。谢谢,我会订阅并希望最终能解决这个问题。 - Some Guy
1
@SUTerliakov 如果你感兴趣的话,可以尝试使用我的回答中所示的 ParamSpec 来完成一些工作。 - blhsing
2个回答

2

正如@SUTerliakov在评论中指出的那样,目前没有直接支持的方法来使用与父类覆盖方法相同的注释来注释子方法。

然而,随着Python 3.10中typing.ParamSpec的引入,一个可行的解决方法是定义一个包装函数,该函数使用父函数对象的参数规范来注释一个内部包装函数,该内部包装函数在对参数进行“一些处理”后实际调用父函数,并返回内部包装函数以使其成为子类的实际方法:

from typing import TypeVar, ParamSpec
from collections.abc import Callable

T = TypeVar('T')
P = ParamSpec('P')

class A:
    def test(self, value: int, *args: str, **kwargs: int) -> str:
        print('base method with:', self, value, args, kwargs)
        return ' '.join(map(str, (value, args, kwargs)))
    
class B(A):
    @staticmethod
    def inherit(f: Callable[P, T]) -> Callable[P, T]:
        def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
            print('do something in wrapper with:', args, kwargs)
            return f(*args, **kwargs)
        return wrapper
    test = inherit(A.test)

print(B().test(1, 'foo', 'bar', a=2))

代码通过mypy的演示:https://mypy-play.net/?mypy=latest&python=3.11&gist=828b3ba31c5c4979ff46ae2b2213dbc4

代码在repl.it上运行的演示:https://replit.com/@blhsing/ThriftyZealousCustomer

PyCharm中类型提示的演示: enter image description here


2
很酷!如果能将inherit变成一个决策工厂就更好了(不属于类,只是简单地包装类型检查),这样它就不会属于类并且可以包装一个方法,但不幸的是这是不可行的(主体无法得到适当的类型检查)。 - SUTerliakov

1
如果您不想在if not typing.TYPE_CHECKING块下定义B.test,那么很容易制作一个空操作的装饰器工厂来接管您需要的任何签名:
import collections.abc as cx
import typing as t


P = t.ParamSpec("P")
R = t.TypeVar("R")


def withSignatureFrom(
    f: cx.Callable[P, R]
) -> cx.Callable[[cx.Callable[..., object]], cx.Callable[P, R]]:
    return lambda _: _  # type: ignore[return-value]


class A:
    def test(self, value: int, *args: str, **kwargs: int) -> str:
        return ""


class B(A):
    @withSignatureFrom(A.test)
    def test(self, value, *args, **kwargs):
        # do some stuff
        return super().test(value)

>>> B().test("")  # mypy: Argument 1 to "test" of "B" has incompatible type "str"; expected "int" [arg-type]

这仅处理实例方法 - 如果要处理 @staticmethod@classmethod,则需要定义其他 @typing.overload,并在其中使用 @withSignatureFrom


如果在 --strict 模式下运行 mypy,您还需要决定如何处理未标记类型的定义;例如通过设置配置 disallow_untyped_defs = False


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接