修饰 Python 类的所有函数,同时能够访问装饰器中的类属性

Decorate all functions of a Python class while being able to access the class attributes within the decorator

提问人:Strawbert 提问时间:8/28/2023 更新时间:8/28/2023 访问量:57

问:

我有一个类,并希望将装饰器应用于该类中的所有函数,而不必为每个函数添加函数装饰器。 我知道有像这里解释的解决方案 如何装饰类的所有函数,而无需为每个方法一遍又一遍地键入它? 将装饰器添加到整个类。但是,我需要能够访问装饰器中的所有类属性。

因此,像这样使用其他解决方案中的装饰器,我需要能够访问 f 中的所有 cls 属性

def function_decorator(orig_func):
    def decorator(*args, **kwargs):
        print("Decorating wrapper called for method %s" % orig_func.__name__)
        result = orig_func(*args, **kwargs)
        return result
    return decorator

def class_decorator(decorator):
    def decorate(cls):
        # this doesn't work
        # print(cls.name)
        for attr in cls.__dict__: 
            if callable(getattr(cls, attr)):
                setattr(cls, attr, decorator(getattr(cls, attr)))
        return cls
    return decorate

@class_decorator(function_decorator)
class PersonWithClassDecorator:
    def __init__(self, name):
        self.name = name

    def print_name(self):
        print(self.name) 

me = PersonWithClassDecorator("Me")
me.print_name()

cls 变量的类型为 <class 'main.PersonWithClassDecorator>。

有没有人对如何实现这一目标有想法?我还研究了元类,但遇到了无法访问类属性的相同问题。任何帮助都非常感谢:)

python 装饰 python类 装饰器

评论

0赞 Nullman 8/28/2023
你可以利用类本身来做你想做的事,然后你就可以访问类了__getattribute__self
0赞 Nikolay Hüttenberend 8/28/2023
它的定义方式不会在要访问/打印属性的阶段初始化。它更像是一个实例,而不是一个类属性。在创建之前,将对类进行修饰,因此 的值尚未设置。还是我看错了?namePersonWithClassDecorator"Me"

答:

1赞 Wombatz 8/28/2023 #1

备选方案1

正如@Nullman建议的那样,您可以将装饰品移入__getattribute__中。每当从类的实例访问任何属性(包括方法)时,都会调用此方法。

你可以直接在类中实现,也可以创建一个只包含装饰的 mixin。__getattribute__

from typing import Any

class Decorate:
    def __getattribute__(self, item: str) -> Any:
        val = super().__getattribute__(item)
        
        if callable(val):
            print("Name", self.name)  # you can access the instance name here
            return function_decorator(val)
        return val

然后从 继承。Decorator

class PersonWithClassDecorator(Decorate): ...

这种简单的方法有两个缺点:

  • 被硬编码到类中function_decoratorDecorate
  • dunder-methods like ,或者所有的运算符钩子都没有修饰(在内部,它们不是从实例访问的,而是从类访问的)__init____str__

注意:如果您使用 (you should ;-) ),钩子将禁用对不存在属性的检测。要解决此问题,请将定义包装在一个块中。mypy__getattribute____getattribute__if not typing.TYPE_CHECKING

备选方案2

实际上可以访问实例属性。您可以相应地更改它并使用原始方法。function_decorator

from collections.abc import Callable
from typing import Any

def function_decorator(orig_function: Callable[..., Any]) -> Callable[..., Any]:
    def decorator(self, *args: Any, **kwargs: Any) -> Any:
        print(f"Decorating wrapper called for method {orig_func.__name__}: {self.name}")
        return orig_function(self, *args, **kwargs)
    return decorator

注意:我添加了类型提示,并用-strings替换了旧的基于-string的字符串格式。%f

这有一个小问题:只在被调用之后才定义。您可以通过不同的方式处理此问题:self.name__init__

  • getattr(self, 'name', None) - self.name在 init 之前None
  • 不要装饰:__init__if orig_func.__name__ == '__init__': return orig_func
  • 使用不同的装饰器__init__

缺点:

  • 您还必须处理和@classmethod@staticmethod
  • 现在已耦合到您的类function_decorator