【问题标题】:How to properly type hint a class decorator?如何正确键入提示类装饰器?
【发布时间】:2021-11-18 15:29:01
【问题描述】:

假设我们有一些函数 func 将类 A 的实例映射到类 B 的实例,即它具有签名 Callable[[A], B]

我想为A 的子类编写一个类装饰器autofunc,它会在创建实例时自动将func 应用于实例。例如,考虑基于全局环境变量的自动 jit 编译。这可以通过

from functools import wraps

def autofunc(basecls):
    @wraps(basecls, updated=())

    class WrappedClass(basecls):
        def __new__(cls, *args, **kwargs):
            instance = basecls(*args, **kwargs)
            return func(instance)
    return WrappedClass

那么下面两个大致等价:

<b>class</b> C(A):
...

instance = func(C())

@autofunc
<b>class</b> C(A):
...

instance = C()

天真的我试过了

def autofunc(basecls: type[A]) -> type[B]:

    @wraps(basecls, updated=())
    class WrappedClass(basecls):

        def __new__(cls, *args, **kwargs):
            instance = basecls(*args, **kwargs)
            return func(instance)

    return WrappedClass

mypy 真的很不喜欢,引发错误:

  • error: Variable "basecls" is not valid as a type [valid-type]
  • error: Invalid base class "basecls" [misc]
  • 另外,WrapperClass.__new__ 返回的实例是 B 而不是 WrapperClass

目前有什么方法可以正确键入提示这样的类装饰器,或者mypy 还不能工作吗?


示例代码:
from functools import wraps


class A:
    pass


class B:
    pass


def func(cl: A) -> B:
    print(f"Replacing {cl=}")
    return B()


def autofunc(basecls: type[A]) -> type[B]:

    @wraps(basecls, updated=())
    class WrappedClass(basecls):

        def __new__(cls, *args, **kwargs):
            instance = basecls()
            return func(instance)

    return WrappedClass

【问题讨论】:

  • 怀疑这会解决问题,但不应该是type[A] -&gt; type[A]吗?
  • @joel 我不确定。问题在于,就目前的编写方式而言,WrapperClass 本身的定义并不明确,因为它是basecls 的子类,即type[A],但__new__ 构造函数返回type[B] 的对象。 MyPy 也理所当然地抱怨这一点,但我还没有找到更好的选择。我写type[A] -&gt; type[B] 的原因是func 映射A -&gt; B。事实上,从技术上讲,autofunc 确实映射了type[A] -&gt; type[A],但在实例化之后你总是得到type[B]
  • 是的,好点。在相关说明中,如果B 不是A,则从A__new__ 返回B 是否有效?我原以为这真的会打破一些 OO/类型假设
  • @joel 它是有效的、可工作的 python 代码。但当然它与类和__new__ 应该如何工作的心理模型相冲突。

标签: type-hinting mypy python-typing python-3.9 python-3.10


【解决方案1】:

这只是mypy:https://github.com/python/mypy/issues/5865中的一个错误

【讨论】:

    猜你喜欢
    • 2021-01-17
    • 2023-01-13
    • 1970-01-01
    • 2021-09-30
    • 1970-01-01
    • 1970-01-01
    • 2019-11-17
    • 1970-01-01
    • 2018-04-14
    相关资源
    最近更新 更多