Python 将装饰程序附加到类中的所有函数

Python 将装饰程序附加到类中的所有函数,python,class,oop,decorator,class-method,Python,Class,Oop,Decorator,Class Method,我真的不需要这样做,但我只是想知道,有没有一种方法可以将装饰器一般地绑定到类中的所有函数,而不是为每个函数显式地声明它 我想它会变成一种方面,而不是一个装饰器,它确实感觉有点奇怪,但考虑到计时或身份验证之类的东西,它会非常整洁。您可以覆盖\uu getattr\uu方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。您可能希望执行以下操作: class Eggs(object): def __getattr__(self, attr): return decorate

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


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

您可以覆盖
\uu getattr\uu
方法。它实际上并没有附加装饰器,但它允许您返回装饰方法。您可能希望执行以下操作:

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

有一些丑陋的递归隐藏在其中,您可能想对其进行保护,但这只是一个开始。

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

或者,只需在类定义的末尾使用以下命令应用修饰符:


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

每次你想更改类定义时,你都可以使用类修饰器或元类。e、 g.使用元类

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()
输出:

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

注意:它不会修饰staticmethod和classmethod,当然,当您想要修改python创建对象的方式时,元类是最适合python的方式。这可以通过重写类的
\uuuu new\uuu
方法来完成。但围绕这个问题(特别是Python3.X),我想提到以下几点:

  • types.FunctionType
    不保护特殊方法不被修饰,因为它们是函数类型。作为一种更通用的方式,您可以只装饰名称不以双下划线(
    \uuuuu
    )开头的对象。此方法的另一个好处是,它还涵盖了命名空间中存在的对象,这些对象以
    \uuuuu
    开头,但不是像
    \uuuuuuuuqalname\uuuuu
    \uuuuuu模块
    这样的函数
  • 的头中的
    名称空间
    参数在
    \uuuuuuu new\uuuuu
    中不包含类属性。原因是
    \uuuu new\uuuu
    \uuuu init\uuuuu
    (初始化)之前执行

  • 没有必要使用
    classmethod
    作为decorator,因为在大多数情况下,您都是从另一个模块导入decorator的

  • 如果您的类包含一个全局项(位于
    \uu init\uuuuuu
    的外侧),用于拒绝在检查名称是否以
    \uuuuu
    开头的同时进行修饰,则可以使用
    类型.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)
    
    演示:

    输出:

    before func1
    after func1
    
    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)
    ,查看结果,然后在
    \uuuuu new\uuuu
    中更改字典理解,如下所示,然后再次查看结果:

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

    class-DecoMeta(类型):
    定义(cls、名称、基数、属性):
    对于attr\u name,attrs.items()中的attr\u值:
    如果isinstance(属性值,类型.函数类型):
    attrs[attr\u name]=cls.deco(attr\u值)
    返回super(DecoMeta,cls)。\uuuu new\uuuu(cls,name,base,attrs)
    @类方法
    def装饰(cls,func):
    def包装(*args,**kwargs):
    打印(“之前”,函数名)
    结果=函数(*args,**kwargs)
    打印(“之后”,函数名)
    返回结果
    返回包装器
    

    (感谢Duncan的帮助)

    以下代码适用于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)
    

    我将在这里重复我的答案,以获得类似的结果

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

    通过更改元类

    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
    
    最近,我遇到了同样的问题,但我不能将decorator放在类上或以任何其他方式更改它,除非我被允许只通过继承添加这样的行为(如果您可以随心所欲地更改代码库,我不确定这是否是最佳选择)

    这里class
    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
    

    在某些情况下,您可能还想做另一件稍微类似的事情。有时,您希望为调试之类的事情触发附件,而不是在所有类上,而是在对象的每个方法上,您可能希望记录它正在执行的操作

    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),并用调试装饰器替换所有不以u开头的函数和方法


    上面没有提到只迭代dir(self)的方法,但它完全有效。在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
    

    你是说
    \uuu getattribute\uuuu
    ?@JaceBrowning:不,我不这么认为。如果Eggs.attr实际上被称为Eggs.\u attr,这将起作用。如果您想覆盖对Eggs.attr的访问,这是一个实际属性,那么可能。这就是棘手的递归方法的用武之地。我知道你现在在做什么——你未修饰的方法都是从
    \uuu
    开始的。您可以使用
    \uuuu getattribute\uuuu
    来避免这种情况,但您是对的,其中有一些棘手的递归需要避免。非常好的信息。我使用这种技术来创建t
    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