关于python:将decorator附加到类中的所有函数

Attaching a decorator to all functions within a class

我不需要这样做,只是想知道,有没有一种方法可以将修饰符绑定到类中的所有函数,而不是为每个函数显式地声明它。

我想它会变成一种方面,而不是一个装饰,它确实感觉有点奇怪,但在考虑时间或授权之类的事情时,它会非常整洁。


每当您考虑更改类定义时,都可以使用类修饰器或元类。例如,使用元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import types

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

      for attr_name, attr_value in attrs.iteritems():
         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(object):
   __metaclass__ = DecoMeta

   def func1(self):
      pass

MyKlass().func1()

输出:

1
2
before func1
after func1

注意:它不会修饰staticmethod和classmethod


要做到这一点,或者对类定义进行其他修改,最干净的方法是定义一个元类。

或者,只需在类定义的末尾应用decorator:

1
2
3
4
5
6
class Something:
   def foo(self): pass

for name, fn in inspect.getmembers(Something):
    if isinstance(fn, types.UnboundMethodType):
        setattr(Something, name, decorator(fn))

对于python 3,将types.unbundMethodType替换为types.functionType。

当然,在实践中,你会更加有选择地应用你的装饰器,一旦你想装饰除一种方法以外的所有方法,你就会发现,仅仅以传统的方式使用装饰器语法更容易、更灵活。


当然,当您想要修改Python创建对象的方式时,元类是最简单的方法。这可以通过重写类的__new__方法来实现。但关于这个问题(特别是针对python 3.x),我想提一些要点:

  • types.FunctionType不保护特殊方法不被修饰,因为它们是函数类型。作为一种更一般的方法,您可以只修饰那些名称不是以双下划线开头的对象(__)。此方法的另一个好处是它还覆盖了那些存在于命名空间中并且以__开头但功能与__qualname____module__等不同的对象。
  • __new__头中的namespace参数不包含__init__中的类属性。原因是__new____init__初始化之前执行。

  • 没有必要使用classmethod作为装饰器,因为在大多数情况下,您从另一个模块导入装饰器。

  • 如果您的类包含一个全局项(在__init__的外部),用于拒绝在检查名称是否以__开头的同时进行修饰,则可以使用types.FunctionType检查类型,以确保您没有修饰非函数对象。
  • 以下是您可以使用的元计算示例:

    1
    2
    3
    4
    5
    6
    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)

    演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    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)

    输出:

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

    对于上述注释中的第3项,您可以取消对行a = 10的注释,并执行print(myinstance.a)并查看结果,然后更改__new__中的字典理解,如下所示,然后再次查看结果:

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

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

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

    有一些丑陋的递归隐藏在那里,你会想保护,但这是一个开始。