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

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

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

因此,像这样使用其他解决方案中的装饰器,我需要能够访问 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 变量的类型为 main.PersonWithClassDecorator'>。

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

python decorator python-decorators python-class class-decorator
1个回答
0
投票

替代方案1

正如@Nullman建议的那样,您可以将装饰移动到

__getattribute__
-hook中。每当从类的实例访问任何属性(包括方法)时都会调用此方法。

您可以直接在类中实现

__getattribute__
或创建一个仅包含装饰的 mixin。

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_decorator
    被硬编码到
    Decorate
    类中
  • __init__
    __str__
    这样的dunder方法或所有操作符钩子都没有被修饰(在内部它们不是从实例访问,而是从类访问)

注意:如果您使用

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

注意:我添加了类型提示,并用

%
-字符串替换了旧的基于
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
    现在已与您的班级耦合
© www.soinside.com 2019 - 2024. All rights reserved.