【问题标题】:Writing a class decorator that applies a decorator to all methods编写一个将装饰器应用于所有方法的类装饰器
【发布时间】:2011-10-05 11:14:23
【问题描述】:

我正在尝试编写一个类装饰器,将装饰器应用于所有类的方法:

import inspect


def decorate_func(func):
    def wrapper(*args, **kwargs):
        print "before"
        ret = func(*args, **kwargs)
        print "after"
        return ret
    for attr in "__module__", "__name__", "__doc__":
        setattr(wrapper, attr, getattr(func, attr))
    return wrapper


def decorate_class(cls):
    for name, meth in inspect.getmembers(cls, inspect.ismethod):
        setattr(cls, name, decorate_func(meth))
    return cls


@decorate_class
class MyClass(object):

    def __init__(self):
        self.a = 10
        print "__init__"

    def foo(self):
        print self.a

    @staticmethod
    def baz():
        print "baz"

    @classmethod
    def bar(cls):
        print "bar"


obj = MyClass()
obj.foo()
obj.baz()
MyClass.baz()
obj.bar()
MyClass.bar()

它几乎可以工作,但@classmethodS 需要特殊处理:

$ python test.py
before
__init__
after
before
10
after
baz
baz
before
Traceback (most recent call last):
  File "test.py", line 44, in <module>
    obj.bar()
  File "test.py", line 7, in wrapper
    ret = func(*args, **kwargs)
TypeError: bar() takes exactly 1 argument (2 given)

有没有办法很好地处理这个问题?我检查了 @classmethod 修饰的方法,但我看不出有什么可以将它们与其他“类型”的方法区分开来。

更新

这里是记录的完整解决方案(使用描述符很好地处理@staticmethodS 和@classmethodS,以及aix 的技巧来检测@classmethodS VS 普通方法):

import inspect


class DecoratedMethod(object):

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, cls=None):
        def wrapper(*args, **kwargs):
            print "before"
            ret = self.func(obj, *args, **kwargs)
            print "after"
            return ret
        for attr in "__module__", "__name__", "__doc__":
            setattr(wrapper, attr, getattr(self.func, attr))
        return wrapper


class DecoratedClassMethod(object):

    def __init__(self, func):
        self.func = func

    def __get__(self, obj, cls=None):
        def wrapper(*args, **kwargs):
            print "before"
            ret = self.func(*args, **kwargs)
            print "after"
            return ret
        for attr in "__module__", "__name__", "__doc__":
            setattr(wrapper, attr, getattr(self.func, attr))
        return wrapper


def decorate_class(cls):
    for name, meth in inspect.getmembers(cls):
        if inspect.ismethod(meth):
            if inspect.isclass(meth.im_self):
                # meth is a classmethod
                setattr(cls, name, DecoratedClassMethod(meth))
            else:
                # meth is a regular method
                setattr(cls, name, DecoratedMethod(meth))
        elif inspect.isfunction(meth):
            # meth is a staticmethod
            setattr(cls, name, DecoratedClassMethod(meth))
    return cls


@decorate_class
class MyClass(object):

    def __init__(self):
        self.a = 10
        print "__init__"

    def foo(self):
        print self.a

    @staticmethod
    def baz():
        print "baz"

    @classmethod
    def bar(cls):
        print "bar"


obj = MyClass()
obj.foo()
obj.baz()
MyClass.baz()
obj.bar()
MyClass.bar()

【问题讨论】:

  • 你的 DecoratedClassMethod 和 DecoratedMethod 类是完全一样的。请编辑以提出正确的解决方案。
  • 它们不同:DecoratedMethod 传递对象实例,而 DecoratedClassMethod 不传递。
  • 这些非常相似,因此必须可以将它们组合起来以避免重复。按照self.func(cls or obj, *args, **kwargs) 的思路思考。我知道这不一样,但是一个简单的if 语句和正确的测试最终会让你免于拥有这两个几乎相同的类。

标签: python decorator


【解决方案1】:

以上答案并不直接适用于 python3。基于其他很好的答案,我已经能够提出以下解决方案:

import inspect
import types
import networkx as nx


def override_methods(cls):
    for name, meth in inspect.getmembers(cls):
        if name in cls.methods_to_override:
            setattr(cls, name, cls.DecorateMethod(meth))
    return cls


@override_methods
class DiGraph(nx.DiGraph):

    methods_to_override = ("add_node", "remove_edge", "add_edge")

    class DecorateMethod:

        def __init__(self, func):
            self.func = func

        def __get__(self, obj, cls=None):
            def wrapper(*args, **kwargs):
                ret = self.func(obj, *args, **kwargs)
                obj._dirty = True  # This is the attribute I want to update
                return ret
            return wrapper

    def __init__(self):
        super().__init__()
        self._dirty = True

现在,只要调用元组methods_to_override 中的方法,就会设置脏标志。当然,其他任何东西也可以放在那里。没有必要在需要重写其方法的类中包含DecorateMethod 类。但是,由于DecorateMehod 使用类的特定属性,我更喜欢创建一个类属性。

【讨论】:

    【解决方案2】:

    (评论太长)

    我冒昧地添加了指定应该为您的解决方案装饰哪些方法的功能:

    def class_decorator(*method_names):
    
        def wrapper(cls):
    
            for name, meth in inspect.getmembers(cls):
                if name in method_names or len(method_names) == 0:
                    if inspect.ismethod(meth):
                        if inspect.isclass(meth.im_self):
                            # meth is a classmethod
                            setattr(cls, name, VerifyTokenMethod(meth))
                        else:
                            # meth is a regular method
                            setattr(cls, name, VerifyTokenMethod(meth))
                    elif inspect.isfunction(meth):
                        # meth is a staticmethod
                        setattr(cls, name, VerifyTokenMethod(meth))
    
            return cls
    
        return wrapper
    

    用法:

    @class_decorator('some_method')
    class Foo(object):
    
        def some_method(self):
            print 'I am decorated'
    
        def another_method(self):
            print 'I am NOT decorated'
    

    【讨论】:

      【解决方案3】:

      inspect.isclass(meth.im_self) 应该告诉你meth 是否是一个类方法:

      def decorate_class(cls):
          for name, meth in inspect.getmembers(cls, inspect.ismethod):
              if inspect.isclass(meth.im_self):
                print '%s is a class method' % name
                # TODO
              ...
          return cls
      

      【讨论】:

      • 成功了,谢谢!我用完整的解决方案更新了我的问题。
      猜你喜欢
      • 2020-11-30
      • 2017-07-17
      • 1970-01-01
      • 2015-02-01
      • 2020-01-11
      • 2016-07-24
      • 1970-01-01
      • 2014-01-14
      • 2021-04-20
      相关资源
      最近更新 更多