【问题标题】:How to add type annotation to self parameter of the decorator of class method?如何在类方法装饰器的 self 参数中添加类型注释?
【发布时间】:2021-11-30 07:35:18
【问题描述】:

所以,我有一个这样的类的装饰器

def something_todo(f):
    @functools.wraps(f)
    def decorator(self, *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return decorator

我想在装饰器中对self 参数进行类型注释。但是,这样写是行不通的

def something_todo(f):
    @functools.wraps(f)
    def decorator(self: SomeClass, *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return decorator

class SomeClass:

    def __init__(self):
        # init here

    @something_todo
    def some_method(self, *args, **kwargs):
        # some process

如果我用不同的脚本编写装饰器和类,情况会变得更糟。这很可能会发生

ImportError: cannot import name 'SomeClass' from partially initialized module 'someclass' (most likely due to a circular import)

我这样做的原因是为了清楚起见,以便人们知道我的装饰器仅适用于该类方法。此外,我可以在编辑器中轻松检查类的所有方法或属性,而无需打开包含该类的脚本。

编辑

所以这是解决方案的回顾:

假设我有 2 个脚本。一个包含class,一个包含装饰器函数。例如,

# inside decorators.py
def something_todo(f):
    @functools.wraps(f)
    def decorator(self, *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return decorator

# inside someclass.py

from decorators import something_todo

class SomeClass:

    def __init__(self):
        # init here

    @something_todo
    def some_method(self, *args, **kwargs):
        # some process

如果我想在装饰器中为self参数添加类型注释,我不能这样做

# inside decorators.py

from someclass import SomeClass

def something_todo(f):
    @functools.wraps(f)
    def decorator(self: SomeClass, *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return decorator

因为当我尝试在另一个脚本中导入类时它会引发ImportError。所以,为了防止错误,我可以这样做

# inside decorators.py

from someclass import *

def something_todo(f):
    @functools.wraps(f)
    def decorator(self: "SomeClass", *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return decorator

而且效果很好。

【问题讨论】:

  • “不工作”是什么意思?
  • PEP 612,参数规范变量是否解决了您的困难?
  • @Samwise 无法正常工作,好像它会引发我最后所说的错误。

标签: python annotations python-decorators


【解决方案1】:

两个有用的技巧:

  1. 将类型前向声明为字符串文字,例如self: 'SomeClass'
  2. 使用callable protocols 可以更灵活地定义可调用的TypeVars。
import functools
from typing import cast, Any, Callable, Protocol, TypeVar


class SomeMethod(Protocol):
    def __call__(
        _self,
        self: 'SomeClass',
        *args: Any,
        **kwargs: Any
    ) -> Any: ...


_SomeMethod = TypeVar("_SomeMethod", bound=SomeMethod)


def something_todo(f: _SomeMethod) -> _SomeMethod:
    @functools.wraps(f)
    def wrapper(self: SomeClass, *args, **kwargs):
        # do something
        return f(self, *args, **kwargs)
    return cast(_SomeMethod, wrapper)


class SomeClass:

    def __init__(self):
        # init here
        pass

    @something_todo
    def some_method(self, *args, **kwargs):
        # some process
        pass


@something_todo
def foo():
    print('error: Value of type variable "_SomeMethod" of "something_todo" cannot be "Callable[[], None]"')

【讨论】:

  • 这两个技巧都能解决循环导入错误吗?因为我有太多的装饰器,所以我必须用与类脚本不同的脚本来编写它们。
  • 应该可以正常工作,但我没有要测试的实际代码。我有点好奇为什么你有这么多只在一个类中使用的装饰器——听起来这个类本身可能需要分解。
  • 我正在使用装饰器,因为我意识到每次运行方法时都需要更改状态(从数据库),并在完成后将其更改回来,以防止在方法仍在运行时调用其他方法。如果我手动添加和附加我的​​方法,它将需要做很多工作。这就是我使用装饰器的原因。
  • 对于上下文管理器来说听起来不错。
  • 另外——你有很多不同的装饰器来改变不同的数据库状态吗?参数化是一件好事(你可以拥有将参数传递给装饰器的装饰器函数,这显然也适用于上下文管理器函数),而不是复制+粘贴同一函数的一堆不同版本。
【解决方案2】:

你没有返回装饰器函数。尝试添加行return decorator。如果我正确理解了您的问题,则实施没有任何问题。

from functools import wraps
def something_todo(f):
    @wraps(f)
    def decorator(self, *args, **kwargs):
        # do something
        print("test decorator")
        return f(self, *args, **kwargs)
    return decorator

class SomeClass:

    def __init__(self):
        pass

    @something_todo
    def some_method(self, *args, **kwargs):
        print("test some method")
    
SomeClass().some_method()

打印:

test decorator
test some method

【讨论】:

  • 这不是问题。我的问题是关于类型注释。我已经修复了缺少的返回装饰器。
猜你喜欢
  • 2022-09-21
  • 2011-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-13
  • 2011-07-25
  • 2019-11-13
相关资源
最近更新 更多