【问题标题】:Attaching a decorator to all functions within a class将装饰器附加到类中的所有函数
【发布时间】:2011-03-28 21:46:30
【问题描述】:

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

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

【问题讨论】:

    标签: python class oop decorator class-method


    【解决方案1】:

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

    或者,只需使用 inspect 在类定义的末尾应用您的装饰器:

    import inspect
    
    class Something:
        def foo(self): 
            pass
    
    for name, fn in inspect.getmembers(Something, inspect.isfunction):
        setattr(Something, name, decorator(fn))
    

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

    【讨论】:

    • 如果您不希望未绑定的方法(即当您在类的主体之外定义一个函数,然后执行class Something: foo = function_you_defined 之类的操作)进行修饰,则使用inspect.ismethod 而不是inspect.isfunction。另请注意,即使使用isfunction,内置函数也不会被装饰,例如如果你有class Sommething: foo = len,那么答案中的代码,foo 不会被装饰,你需要使用inspect.isroutine 来处理这种情况。
    【解决方案2】:

    在 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
    

    【讨论】:

    • 这很有希望,但如果你的类中有任何属性,就会出现问题。
    【解决方案3】:

    Python 3 更新:

    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(DecoMeta, cls).__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
    

    (感谢邓肯)

    【讨论】:

    • if isinstance 语句之后需要一个缩进块。感谢 3.X 的更新
    • 还应该包括import types。您还可以将DecoMeta, clssuper 调用中删除为return super().__new__(cls, name, bases, attrs)。在 python3 中使用它是class MyKlass(metaclass=MetaGetUserData):。感谢 3.X 的更新!
    【解决方案4】:

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

    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) 的方法,但完全有效。并且可以从对象外部调用,而且可以任意调用。

    【讨论】:

      【解决方案5】:

      我将在这里重复我的答案,类似issue

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

      通过更改元类

      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
      

      【讨论】:

      • 您的装饰器解决方案似乎不允许从logger 装饰器访问self
      【解决方案6】:

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

      【讨论】:

        【解决方案7】:

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

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

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

        4. 如果您的类包含一个全局项(在__init__ 之外),用于拒绝被装饰,同时检查名称是否不是以__ 开头的,您可以使用types.FunctionType 检查类型以确保你不是在装饰一个非功能对象。

        这是您可以使用的示例 metacals:

        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.
        

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

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

        【讨论】:

          【解决方案8】:

          每当您想到更改类定义时,您可以使用类装饰器或元类。例如使用元类

          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
          

          注意:它不会修饰staticmethod和classmethod

          【讨论】:

          【解决方案9】:

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

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

          隐藏在其中的一些丑陋的递归是您想要防止的,但这是一个开始。

          【讨论】:

          • 你不是说__getattribute__吗?
          • @JaceBrowning:不,我不这么认为。如果 Eggs.attr 实际上被称为 Eggs._attr,这将起作用。如果您想覆盖对 Eggs.attr 的访问,这是一个实际属性,那么也许可以。不过,这就是棘手的递归问题的来源。
          • 我明白你现在在做什么——你未修饰的方法都以_开头。你可以使用__getattribute__ 来避免这种情况,但你是对的,有一些棘手的递归需要避免。
          猜你喜欢
          • 2016-02-25
          • 2018-06-05
          • 2017-04-20
          • 2019-05-16
          • 2011-06-06
          • 1970-01-01
          • 2011-10-04
          • 2023-01-16
          相关资源
          最近更新 更多