如何给class方法装饰器的self参数添加类型注解?

How to add type annotation to self parameter of the decorator of class method?

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

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

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

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

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

的脚本

编辑

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

假设我有 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

因为当我试图在另一个脚本中导入 class 时它会引发 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

而且效果很好。

两个有用的技巧:

  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]"')

您没有 return 装饰函数。尝试添加行 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