将装饰器附加到类中的所有函数

发布于 2024-09-14 12:29:05 字数 110 浏览 10 评论 0原文

有没有一种方法可以将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式声明它?

我想它会成为一种方面,而不是装饰器,它确实感觉有点奇怪,但考虑到诸如计时或身份验证之类的东西,它会非常整洁。

Is there a way to bind a decorator to all functions within a class generically, rather than explicitly stating it for every function?

I suppose it then becomes a kind of aspect, rather than a decorator and it does feel a bit odd, but was thinking for something like timing or auth it'd be pretty neat.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

手心的海 2024-09-21 12:29:05

执行此操作或对类定义进行其他修改的最简洁方法是定义元类。

或者,只需使用 inspect

import inspect

class Something:
    def foo(self): 
        pass

for name, fn in inspect.getmembers(Something, inspect.isfunction):
    setattr(Something, name, decorator(fn))

在实践中,您当然会希望更有选择性地应用装饰器。一旦您想要装饰除一个方法之外的所有方法,您就会发现以传统方式使用装饰器语法更容易、更灵活。

The cleanest way to do this, or to do other modifications to a class definition, is to define a metaclass.

Alternatively, just apply your decorator at the end of the class definition using inspect:

import inspect

class Something:
    def foo(self): 
        pass

for name, fn in inspect.getmembers(Something, inspect.isfunction):
    setattr(Something, name, decorator(fn))

In practice of course you'll want to apply your decorator more selectively. As soon as you want to decorate all but one method you'll discover that it is easier and more flexible just to use the decorator syntax in the traditional way.

旧话新听 2024-09-21 12:29:05

每次您想要更改类定义时,都可以使用类装饰器或元类。例如使用元类:

import types

class DecoMeta(type):
   def __new__(cls, name: str, bases: tuple, attrs: dict):
      for attr_name, attr_value in attrs.items():
         if isinstance(attr_value, types.FunctionType):
            attrs[attr_name] = cls.deco(attr_value)
            
      return super(DecoMeta, cls).__new__(cls, name, bases, attrs)
   
   @classmethod
   def deco(cls, func):
      def wrapper(*args, **kwargs):
         print "before",func.func_name
         result = func(*args, **kwargs)
         print "after",func.func_name
         return result
      return wrapper
   
class MyKlass(metaclass=DecoMeta):
   def func1(self): 
      pass
  
MyKlass().func1()

输出:

before func1
after func1

注意:它不会装饰静态方法和类方法。

Every time you think of changing a class definition, you can either use the class decorator or metaclass. E.g. using metaclass:

import types

class DecoMeta(type):
   def __new__(cls, name: str, bases: tuple, attrs: dict):
      for attr_name, attr_value in attrs.items():
         if isinstance(attr_value, types.FunctionType):
            attrs[attr_name] = cls.deco(attr_value)
            
      return super(DecoMeta, cls).__new__(cls, name, bases, attrs)
   
   @classmethod
   def deco(cls, func):
      def wrapper(*args, **kwargs):
         print "before",func.func_name
         result = func(*args, **kwargs)
         print "after",func.func_name
         return result
      return wrapper
   
class MyKlass(metaclass=DecoMeta):
   def func1(self): 
      pass
  
MyKlass().func1()

Output:

before func1
after func1

Note: it will not decorate staticmethods and classmethods.

很快妥协 2024-09-21 12:29:05

以下代码适用于 python2.x 和 3.x

import inspect

def decorator_for_func(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 decorator_for_class(cls):
    for name, method in inspect.getmembers(cls):
        if (not inspect.ismethod(method) and not inspect.isfunction(method)) or inspect.isbuiltin(method):
            continue
        print("Decorating function %s" % name)
        setattr(cls, name, decorator_for_func(method))
    return cls

@decorator_for_class
class decorated_class:
     def method1(self, arg, **kwargs):
         print("Method 1 called with arg %s" % arg)
     def method2(self, arg):
         print("Method 2 called with arg %s" % arg)


d=decorated_class()
d.method1(1, a=10)
d.method2(2)

Following code works for python2.x and 3.x

import inspect

def decorator_for_func(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 decorator_for_class(cls):
    for name, method in inspect.getmembers(cls):
        if (not inspect.ismethod(method) and not inspect.isfunction(method)) or inspect.isbuiltin(method):
            continue
        print("Decorating function %s" % name)
        setattr(cls, name, decorator_for_func(method))
    return cls

@decorator_for_class
class decorated_class:
     def method1(self, arg, **kwargs):
         print("Method 1 called with arg %s" % arg)
     def method2(self, arg):
         print("Method 2 called with arg %s" % arg)


d=decorated_class()
d.method1(1, a=10)
d.method2(2)
薆情海 2024-09-21 12:29:05

Python 3 更新:(

import types


class DecoMeta(type):
    def __new__(cls, name, bases, attrs):

        for attr_name, attr_value in attrs.items():
            if isinstance(attr_value, types.FunctionType):
                attrs[attr_name] = cls.deco(attr_value)

        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def deco(cls, func):
        def wrapper(*args, **kwargs):
            print("before",func.__name__)
            result = func(*args, **kwargs)
            print("after",func.__name__)
            return result
        return wrapper

感谢 Duncan)

Update for Python 3:

import types


class DecoMeta(type):
    def __new__(cls, name, bases, attrs):

        for attr_name, attr_value in attrs.items():
            if isinstance(attr_value, types.FunctionType):
                attrs[attr_name] = cls.deco(attr_value)

        return super().__new__(cls, name, bases, attrs)

    @classmethod
    def deco(cls, func):
        def wrapper(*args, **kwargs):
            print("before",func.__name__)
            result = func(*args, **kwargs)
            print("after",func.__name__)
            return result
        return wrapper

(and thanks to Duncan for this)

夏日浅笑〃 2024-09-21 12:29:05

当然,当你想要修改 python 创建对象的方式时,元类是最 Pythonic 的方法。这可以通过重写类的 __new__ 方法来完成。但围绕这个问题(特别是 python 3.X),我想提一下:

  1. types.FunctionType 不能保护特殊方法不被修饰,因为它们是函数类型。作为更通用的方法,您可以装饰名称不以双下划线(__)开头的对象。此方法的另一个好处是,它还涵盖了命名空间中存在且以 __ 开头但不像 __qualname__ 、__module__ 那样具有功能的对象。等
  2. __new__ 标头中的 namespace 参数不包含 __init__ 中的类属性。原因是 __new____init__(初始化)之前执行。

  3. 没有必要使用classmethod作为装饰器,就像大多数时候您从另一个模块导入装饰器一样。

  4. 如果您的类包含一个全局项(在 __init__ 之外),用于拒绝装饰并检查名称是否不以 __ 开头,您可以使用以下命令检查类型types.FunctionType 以确保您没有装饰非函数对象。

以下是您可以使用的示例元计算:

class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # if your decorator is a class method of the metaclass  use
        # `my_decorator = cls.my_decorator` in order to invoke the decorator.
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)

演示:

def my_decorator(func):
        def wrapper(self, arg):
            # You can also use *args instead of (self, arg) and pass the *args
            # to the function in following call.
            return "the value {} gets modified!!".format(func(self, arg))
        return wrapper


class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # my_decorator = cls.my_decorator (if the decorator is a classmethod)
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MyClass(metaclass=TheMeta):
    # a = 10
    def __init__(self, *args, **kwargs):
        self.item = args[0]
        self.value = kwargs['value']

    def __getattr__(self, attr):
        return "This class hasn't provide the attribute {}.".format(attr)

    def myfunction_1(self, arg):
        return arg ** 2

    def myfunction_2(self, arg):
        return arg ** 3

myinstance = MyClass(1, 2, value=100)
print(myinstance.myfunction_1(5))
print(myinstance.myfunction_2(2))
print(myinstance.item)
print(myinstance.p)

输出:

the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.

要检查上述注释中的第三项,您可以取消注释行 a = 10 并执行 print(myinstance.a)< /code> 并查看结果,然后按如下方式更改 __new__ 中的字典理解,然后再次查看结果:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
             else my_decorator(v) for k, v in namespace.items()}

Of course that the metaclasses are the most pythonic way to go when you want to modify the way that python creates the objects. Which can be done by overriding the __new__ method of your class. But there are some points around this problem (specially for python 3.X) that I'd like to mention:

  1. types.FunctionType doesn't protect the special methods from being decorated, as they are function types. As a more general way you can just decorate the objects which their names are not started with double underscore (__). One other benefit of this method is that it also covers those objects that exist in namespace and starts with __ but are not function like __qualname__, __module__ , etc.
  2. The namespace argument in __new__'s header doesn't contain class attributes within the __init__. The reason is that the __new__ executes before the __init__ (initializing).

  3. It's not necessary to use a classmethod as the decorator, as in most of the times you import your decorator from another module.

  4. If your class is contain a global item (out side of the __init__) for refusing of being decorated alongside checking if the name is not started with __ you can check the type with types.FunctionType to be sure that you're not decorating a non-function object.

Here is a sample metacalss that you can use:

class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # if your decorator is a class method of the metaclass  use
        # `my_decorator = cls.my_decorator` in order to invoke the decorator.
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)

Demo:

def my_decorator(func):
        def wrapper(self, arg):
            # You can also use *args instead of (self, arg) and pass the *args
            # to the function in following call.
            return "the value {} gets modified!!".format(func(self, arg))
        return wrapper


class TheMeta(type):
    def __new__(cls, name, bases, namespace, **kwds):
        # my_decorator = cls.my_decorator (if the decorator is a classmethod)
        namespace = {k: v if k.startswith('__') else my_decorator(v) for k, v in namespace.items()}
        return type.__new__(cls, name, bases, namespace)


class MyClass(metaclass=TheMeta):
    # a = 10
    def __init__(self, *args, **kwargs):
        self.item = args[0]
        self.value = kwargs['value']

    def __getattr__(self, attr):
        return "This class hasn't provide the attribute {}.".format(attr)

    def myfunction_1(self, arg):
        return arg ** 2

    def myfunction_2(self, arg):
        return arg ** 3

myinstance = MyClass(1, 2, value=100)
print(myinstance.myfunction_1(5))
print(myinstance.myfunction_2(2))
print(myinstance.item)
print(myinstance.p)

Output:

the value 25 gets modified!!
the value 8 gets modified!!
1
This class hasn't provide the attribute p. # special method is not decorated.

For checking the 3rd item from the aforementioned notes you can uncomment the line a = 10 and do print(myinstance.a) and see the result then change the dictionary comprehension in __new__ as follows then see the result again:

namespace = {k: v if k.startswith('__') and not isinstance(v, types.FunctionType)\
             else my_decorator(v) for k, v in namespace.items()}
清泪尽 2024-09-21 12:29:05

对于类似的问题,我将在这里重复我的答案< /a>

可以通过多种不同的方式来完成。我将展示如何通过元类类装饰器继承来实现它

通过更改元类

import functools


class Logger(type):
    @staticmethod
    def _decorator(fun):
        @functools.wraps(fun)
        def wrapper(*args, **kwargs):
            print(fun.__name__, args, kwargs)
            return fun(*args, **kwargs)
        return wrapper

    def __new__(mcs, name, bases, attrs):
        for key in attrs.keys():
            if callable(attrs[key]):
                # if attrs[key] is callable, then we can easily wrap it with decorator
                # and substitute in the future attrs
                # only for extra clarity (though it is wider type than function)
                fun = attrs[key]
                attrs[key] = Logger._decorator(fun)
        # and then invoke __new__ in type metaclass
        return super().__new__(mcs, name, bases, attrs)


class A(metaclass=Logger):
    def __init__(self):
        self.some_val = "some_val"

    def method_first(self, a, b):
        print(a, self.some_val)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
# __init__ (<__main__.A object at 0x7f852a52a2b0>,) {}

b.method_first(5, b="Here should be 5")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 5) {'b': 'Here should be 5'}
# 5 some_val
b.method_first(6, b="Here should be 6")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 6) {'b': 'Here should be 6'}
# 6 some_val
b.another_method(7)
# another_method (<__main__.A object at 0x7f852a52a2b0>, 7) {}
# 7
b.static_method(7)
# 7

此外,还将展示两种方法如何在不更改类的元信息的情况下实现它(通过类装饰器类继承)。第一种方法是通过类装饰器 put_decorator_on_all_methods 接受装饰器来包装类的所有成员可调用对象。

def logger(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)

    return wrapper


def put_decorator_on_all_methods(decorator, cls=None):
    if cls is None:
        return lambda cls: put_decorator_on_all_methods(decorator, cls)

    class Decoratable(cls):
        def __init__(self, *args, **kargs):
            super().__init__(*args, **kargs)

        def __getattribute__(self, item):
            value = object.__getattribute__(self, item)
            if callable(value):
                return decorator(value)
            return value

    return Decoratable


@put_decorator_on_all_methods(logger)
class A:
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(8)
# >>> static_method (8,) {}
# >>> 8

而且,最近,我遇到了同样的问题,但我无法将装饰器放在类上或以任何其他方式更改它,除非我被允许仅通过继承添加此类行为(如果您可以根据需要更改代码库,我不确定这是否是最佳选择)。

这里的 Logger 类强制子类的所有可调用成员写入有关其调用的信息,请参阅下面的代码。

class Logger:

    def _decorator(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f.__name__, args, kwargs)
            return f(*args, **kwargs)

        return wrapper

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Logger):
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7

或者更抽象地讲,您可以基于某些装饰器实例化基类。

def decorator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)
    return wrapper


class Decoratable:
    def __init__(self, dec):
        self._decorator = dec

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Decoratable):
    def __init__(self, dec):
        super().__init__(dec)

    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A(decorator)
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7

I will repeat my answer here, for a similar issue

It can be done many different ways. I will show how to make it through meta-class, class decorator and inheritance.

by changing meta class

import functools


class Logger(type):
    @staticmethod
    def _decorator(fun):
        @functools.wraps(fun)
        def wrapper(*args, **kwargs):
            print(fun.__name__, args, kwargs)
            return fun(*args, **kwargs)
        return wrapper

    def __new__(mcs, name, bases, attrs):
        for key in attrs.keys():
            if callable(attrs[key]):
                # if attrs[key] is callable, then we can easily wrap it with decorator
                # and substitute in the future attrs
                # only for extra clarity (though it is wider type than function)
                fun = attrs[key]
                attrs[key] = Logger._decorator(fun)
        # and then invoke __new__ in type metaclass
        return super().__new__(mcs, name, bases, attrs)


class A(metaclass=Logger):
    def __init__(self):
        self.some_val = "some_val"

    def method_first(self, a, b):
        print(a, self.some_val)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
# __init__ (<__main__.A object at 0x7f852a52a2b0>,) {}

b.method_first(5, b="Here should be 5")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 5) {'b': 'Here should be 5'}
# 5 some_val
b.method_first(6, b="Here should be 6")
# method_first (<__main__.A object at 0x7f852a52a2b0>, 6) {'b': 'Here should be 6'}
# 6 some_val
b.another_method(7)
# another_method (<__main__.A object at 0x7f852a52a2b0>, 7) {}
# 7
b.static_method(7)
# 7

Also, will show two approaches how to make it without changing meta information of class (through class decorator and class inheritance). The first approach through class decorator put_decorator_on_all_methods accepts decorator to wrap all member callable objects of class.

def logger(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)

    return wrapper


def put_decorator_on_all_methods(decorator, cls=None):
    if cls is None:
        return lambda cls: put_decorator_on_all_methods(decorator, cls)

    class Decoratable(cls):
        def __init__(self, *args, **kargs):
            super().__init__(*args, **kargs)

        def __getattribute__(self, item):
            value = object.__getattribute__(self, item)
            if callable(value):
                return decorator(value)
            return value

    return Decoratable


@put_decorator_on_all_methods(logger)
class A:
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)


b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(8)
# >>> static_method (8,) {}
# >>> 8

And, recently, I've come across on the same problem, but I couldn't put decorator on class or change it in any other way, except I was allowed to add such behavior through inheritance only (I am not sure that this is the best choice if you can change codebase as you wish though).

Here class Logger forces all callable members of subclasses to write information about their invocations, see code below.

class Logger:

    def _decorator(self, f):
        @functools.wraps(f)
        def wrapper(*args, **kwargs):
            print(f.__name__, args, kwargs)
            return f(*args, **kwargs)

        return wrapper

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Logger):
    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A()
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7

Or more abstractly, you can instantiate base class based on some decorator.

def decorator(f):
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
        print(f.__name__, args, kwargs)
        return f(*args, **kwargs)
    return wrapper


class Decoratable:
    def __init__(self, dec):
        self._decorator = dec

    def __getattribute__(self, item):
        value = object.__getattribute__(self, item)
        if callable(value):
            decorator = object.__getattribute__(self, '_decorator')
            return decorator(value)
        return value


class A(Decoratable):
    def __init__(self, dec):
        super().__init__(dec)

    def method(self, a, b):
        print(a)

    def another_method(self, c):
        print(c)

    @staticmethod
    def static_method(d):
        print(d)

b = A(decorator)
b.method(5, b="Here should be 5")
# >>> method (5,) {'b': 'Here should be 5'}
# >>> 5
b.method(6, b="Here should be 6")
# >>> method (6,) {'b': 'Here should be 6'}
# >>> 6
b.another_method(7)
# >>> another_method (7,) {}
# >>> 7
b.static_method(7)
# >>> static_method (7,) {}
# >>> 7
若沐 2024-09-21 12:29:05

在某些情况下,您可能还想做另一件稍微相似的事情。有时您想要触发附件以进行诸如调试之类的操作,而不是在所有类上触发附件,但对于对象的每个方法,您可能需要记录其正在执行的操作。

def start_debugging():
        import functools
        import datetime
        filename = "debug-{date:%Y-%m-%d_%H_%M_%S}.txt".format(date=datetime.datetime.now())
        debug_file = open(filename, "a")
        debug_file.write("\nDebug.\n")

        def debug(func):
            @functools.wraps(func)
            def wrapper_debug(*args, **kwargs):
                args_repr = [repr(a) for a in args]  # 1
                kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
                signature = ", ".join(args_repr + kwargs_repr)  # 3
                debug_file.write(f"Calling {func.__name__}({signature})\n")
                value = func(*args, **kwargs)
                debug_file.write(f"{func.__name__!r} returned {value!r}\n")  # 4
                debug_file.flush()
                return value
            return wrapper_debug

        for obj in (self):
            for attr in dir(obj):
                if attr.startswith('_'):
                    continue
                fn = getattr(obj, attr)
                if not isinstance(fn, types.FunctionType) and \
                        not isinstance(fn, types.MethodType):
                    continue
                setattr(obj, attr, debug(fn))

该函数将遍历一些对象(当前仅是 self),并用调试装饰器替换所有不以 _ 开头的函数和方法。

上面没有提到用于迭代 dir(self) 的方法,但完全有效。并且可以从对象外部调用,而且更加任意。

There's another slightly similar thing you might want to do in some cases. Sometimes you want to trigger the attachment for something like debugging and not on all the classes but for every method of an object you might want a record of what it's doing.

def start_debugging():
        import functools
        import datetime
        filename = "debug-{date:%Y-%m-%d_%H_%M_%S}.txt".format(date=datetime.datetime.now())
        debug_file = open(filename, "a")
        debug_file.write("\nDebug.\n")

        def debug(func):
            @functools.wraps(func)
            def wrapper_debug(*args, **kwargs):
                args_repr = [repr(a) for a in args]  # 1
                kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]  # 2
                signature = ", ".join(args_repr + kwargs_repr)  # 3
                debug_file.write(f"Calling {func.__name__}({signature})\n")
                value = func(*args, **kwargs)
                debug_file.write(f"{func.__name__!r} returned {value!r}\n")  # 4
                debug_file.flush()
                return value
            return wrapper_debug

        for obj in (self):
            for attr in dir(obj):
                if attr.startswith('_'):
                    continue
                fn = getattr(obj, attr)
                if not isinstance(fn, types.FunctionType) and \
                        not isinstance(fn, types.MethodType):
                    continue
                setattr(obj, attr, debug(fn))

This function will go through some objects (only self currently) and replace all functions and methods that do not start with _ with a debugging decorator.

The method used for this of just iterating the dir(self) is not addressed above but totally works. And can be called externally from the object and much more arbitrarily.

今天小雨转甜 2024-09-21 12:29:05

在 Python 3 中,您还可以编写一个简单的函数,将装饰器覆盖/应用到某些方法,如下所示:

from functools import wraps
from types import MethodType

def logged(func):
   @wraps(func)
   def wrapper(*args, **kwargs):
      res = func(*args, **kwargs)
      print("logging:", func.__name__, res)
      return res
   return wrapper

class Test:
   def foo(self):
      return 42
   ...

def aspectize(cls, decorator):
   for name, func in cls.__dict__.items():
      if not name.startswith("__"):
         setattr(cls, name, MethodType(decorator(func), cls))  # MethodType is key

aspectize(Test, logged)
t = Test()
t.foo()  # printing "logging: foo 42"; returning 42

In Python 3 you could also write a simple function that overwrites/applies a decorator to certain methods like so:

from functools import wraps
from types import MethodType

def logged(func):
   @wraps(func)
   def wrapper(*args, **kwargs):
      res = func(*args, **kwargs)
      print("logging:", func.__name__, res)
      return res
   return wrapper

class Test:
   def foo(self):
      return 42
   ...

def aspectize(cls, decorator):
   for name, func in cls.__dict__.items():
      if not name.startswith("__"):
         setattr(cls, name, MethodType(decorator(func), cls))  # MethodType is key

aspectize(Test, logged)
t = Test()
t.foo()  # printing "logging: foo 42"; returning 42
只涨不跌 2024-09-21 12:29:05

结合各种答案的信息,这里有一个 DecorateMethods 元类:

class DecorateMethods(type):
    """ Decorate all methods of the class with the decorator provided """

    def __new__(cls, name, bases, attrs, **kwargs):
        try:
            decorator = kwargs['decorator']
        except KeyError:
            raise ValueError('Please provide the "decorator" argument, eg. '
                             'MyClass(..., metaclass=DecorateMethods, decorator=my_decorator)')

        exclude = kwargs.get('exclude', [])

        for attr_name, attr_value in attrs.items():

            if isinstance(attr_value, types.FunctionType) and \
                    attr_name not in exclude and \
                    not attr_name.startswith('__'):
                attrs[attr_name] = decorator(attr_value)

        return super(DecorateMethods, cls).__new__(cls, name, bases, attrs)

用作:

class MyClass(metaclass=DecorateMethods, decorator=my_decorator, exclude=["METHOD_TO_BE_EXCLUDED"]):
    ...

它与单元测试配合得很好,而不是基于函数的解决方案。


归功于 1, 2 以及这个问题的其他答案。

Combining information from various answers, here's a DecorateMethods metaclass:

class DecorateMethods(type):
    """ Decorate all methods of the class with the decorator provided """

    def __new__(cls, name, bases, attrs, **kwargs):
        try:
            decorator = kwargs['decorator']
        except KeyError:
            raise ValueError('Please provide the "decorator" argument, eg. '
                             'MyClass(..., metaclass=DecorateMethods, decorator=my_decorator)')

        exclude = kwargs.get('exclude', [])

        for attr_name, attr_value in attrs.items():

            if isinstance(attr_value, types.FunctionType) and \
                    attr_name not in exclude and \
                    not attr_name.startswith('__'):
                attrs[attr_name] = decorator(attr_value)

        return super(DecorateMethods, cls).__new__(cls, name, bases, attrs)

Used as:

class MyClass(metaclass=DecorateMethods, decorator=my_decorator, exclude=["METHOD_TO_BE_EXCLUDED"]):
    ...

It works nicely with unittests, as opposed to function-based solutions.


Credit to answers in 1, 2 and other answers in this question.

む无字情书 2024-09-21 12:29:05

我是从这个问题来的
如何装饰类的所有函数,而无需为每个方法一遍又一遍地键入它?
我想添加一条注释:

使用类装饰器或替换类方法(例如这个)的答案将不适用于staticmethod
您将得到 TypeError, Unexpected argument 因为您的方法将获取 self/cls 作为第一个参数。
大概是
装饰类不知道 self 方法的装饰器,即使使用 inspect.ismethod 也无法区分它们。

我找到了一个快速解决办法:
我没有仔细检查过它,但它通过了我的(不那么全面的)测试。
使用动态装饰器已经是一种糟糕的方法,因此,作为临时解决方案,它一定是可以的。

TLD:TD 添加 try/ except 以与 staticmethod 一起使用

def log_sent_data(function):
    @functools_wraps(function)
    def decorator(*args, **kwargs):
        # Quickfix
        self, *args = args
        try:  # If method has self/cls/descriptor
            result = function(self, *args, **kwargs)
        except TypeError:
            if args:  # If method is static but has positional args
                result = function(*args, **kwargs)
            else:  # If method is static and doesn't has positional args
                result = function(**kwargs)
        # End of quickfix
        return result
    return decorator

I came to this question from
How to decorate all functions of a class without typing it over and over for each method?
and I want to add a note:

Answers with class decorators or repalcing class methods like this one will not work with staticmethod.
You will get TypeError, unexpected argument because your method will get self/cls as first argument.
Probably the
decorated class doesn't know about decorators of self methods and can't distinguish them even with inspect.ismethod.

I come to such a quick fix:
I have not not checked it closely but it passes my (no so comprehensive) tests.
Using dynamic decorators is already a bad approach, so, it must be okay as a temporary solution.

TLD:TD Add try/except to use with staticmethod

def log_sent_data(function):
    @functools_wraps(function)
    def decorator(*args, **kwargs):
        # Quickfix
        self, *args = args
        try:  # If method has self/cls/descriptor
            result = function(self, *args, **kwargs)
        except TypeError:
            if args:  # If method is static but has positional args
                result = function(*args, **kwargs)
            else:  # If method is static and doesn't has positional args
                result = function(**kwargs)
        # End of quickfix
        return result
    return decorator
倾城花音 2024-09-21 12:29:05

您可以重写 __getattr__ 方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。您可能想做这样的事情:

class Eggs(object):
    def __getattr__(self, attr):
        return decorate(getattr(self, `_` + attr))

那里隐藏着一些丑陋的递归,您需要防止它们,但这只是一个开始。

You could override the __getattr__ method. It's not actually attaching a decorator, but it lets you return a decorated method. You'd probably want to do something like this:

class Eggs(object):
    def __getattr__(self, attr):
        return decorate(getattr(self, `_` + attr))

There's some ugly recursion hiding in there that you'll want to protect against, but that's a start.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文