当包装器具有实例变量时,为类装饰器键入

问题描述 投票:3回答:1

考虑到mypy的当前限制,这个装饰器是否正确输入?我在下面包含了示例用法:

import functools
from typing import TypeVar, Type, Any, cast

C = TypeVar('C', bound=Type[Any])


def singleton(cls: C) -> C:
    """Transforms a class into a Singleton (only one instance can exist)."""

    @functools.wraps(cls)
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        if not wrapper.instance:  # type: ignore  # https://github.com/python/mypy/issues/2087
            wrapper.instance = cls(*args, **kwargs)  # type: ignore  # https://github.com/python/mypy/issues/2087
        return wrapper.instance  # type: ignore  # https://github.com/python/mypy/issues/2087

    wrapper.instance = None  # type: ignore  # https://github.com/python/mypy/issues/2087
    return cast(C, wrapper)


@singleton
class Test:
    pass


if __name__ == '__main__':
    a = Test()
    b = Test()
    print(a is b)

我必须在type: ignore属性出现的行上添加instance,否则mypy会标记这些错误:

error: "Callable[..., Any]" has no attribute "instance"

python python-3.x mypy
1个回答
1
投票

您的函数采用C类型的参数并返回相同类型的结果。因此,根据mypy ab将具有相同的类型。你可以用reveal_type查看。

reveal_type(a) # Revealed type is 'test.Test'
reveal_type(b) # Revealed type is 'test.Test'

无论如何,cast# type: ignore都应该谨慎使用,因为他们告诉mypy相信你(开发人员)这些类型是正确的,即使它无法确认它。

我在你的代码中看到的潜在问题是你用一个函数替换一个类(即Test),这可能会破坏一些代码。例如:

>>> Test
<function Test at 0x7f257dd2bae8>
>>> Test.mro()
AttributeError: 'function' object has no attribute 'mro'

您可以尝试的另一种方法是替换装饰类的__new__方法:

def singleton(cls: C) -> C:
    """Transforms a class into a Singleton (only one instance can exist)."""

    new = cls.__new__

    def _singleton_new(cls, *args, **kwds):
        try:
            inst = cls._instance
        except AttributeError:
            cls._instance = inst = new(cls, *args, **kwds)
        return inst

    cls.__new__ = _singleton_new
    return cls

在这种情况下,您不会替换整个类,因此您不太可能使用该类破坏其他代码:

>>> Test                                                                                                                                                   
test.Test

>>> Test.mro()                                                                                                                                                           
[test.Test, object]

请注意,上面的代码只是一个示例,用于显示当前方法的局限性。因此,您可能不应该按原样使用它,而是寻找更可靠的解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.