Python - 装饰器 - 尝试访问方法的父类

5 投票
2 回答
3078 浏览
提问于 2025-04-16 05:10

这个不行:

def register_method(name=None):
    def decorator(method):
        # The next line assumes the decorated method is bound (which of course it isn't at this point)
        cls = method.im_class
        cls.my_attr = 'FOO BAR'
        def wrapper(*args, **kwargs):
            method(*args, **kwargs)
        return wrapper
    return decorator

装饰器就像电影《盗梦空间》,你越深入,越会觉得混乱。我想在定义方法的时候访问定义这个方法的类,这样我就可以设置或修改这个类的属性。

版本2也不行:

def register_method(name=None):
    def decorator(method):
        # The next line assumes the decorated method is bound (of course it isn't bound at this point).
        cls = method.__class__  # I don't really understand this.
        cls.my_attr = 'FOO BAR'
        def wrapper(*args, **kwargs):
            method(*args, **kwargs)
        return wrapper
    return decorator

我把我出错的代码放在上面,尽管我已经知道它为什么出错,是因为这样可以更清楚地表达我想要做的事情。

2 个回答

2

与其使用元类,在Python 2.6及以上版本中,你应该使用类装饰器。你可以把函数和类的装饰器封装成类的方法,就像这个实际的例子一样。

我用这个例子来配合djcelery;这个问题中重要的部分是“task”方法和这一行“args, kw = self.marked[klass.dict[attr]]”,它隐含地检查“klass.dict[attr]是否在self.marked中”。如果你想用@methodtasks.task而不是@methodtasks.task()作为装饰器,你可以去掉嵌套的定义,改用集合而不是字典来作为self.marked。使用self.marked,而不是像其他答案那样在函数上设置标记属性,可以让这个方法适用于类方法和静态方法,因为它们使用了slots,不允许设置任意属性。这样做的缺点是,函数装饰器必须放在其他装饰器的上面,而类装饰器必须放在下面,这样函数在两者之间不会被修改或重新包装。

class DummyClass(object):
    """Just a holder for attributes."""
    pass

class MethodTasksHolder(object):
    """Register tasks with class AND method decorators, then use as a dispatcher, like so:

    methodtasks = MethodTasksHolder()

    @methodtasks.serve_tasks
    class C:
        @methodtasks.task()
        #@other_decorators_come_below
        def some_task(self, *args):
            pass

        @methodtasks.task()
        @classmethod
        def classmethod_task(self, *args):
            pass

        def not_a_task(self):
            pass

    #..later
    methodtasks.C.some_task.delay(c_instance,*args) #always treat as unbound
        #analagous to c_instance.some_task(*args) (or C.some_task(c_instance,*args))
    #...
    methodtasks.C.classmethod_task.delay(C,*args) #treat as unbound classmethod!
        #analagous to C.classmethod_task(*args)
    """ 
    def __init__(self):
        self.marked = {}

    def task(self, *args, **kw):
        def mark(fun):
            self.marked[fun] = (args,kw)
            return fun
        return mark

    def serve_tasks(self, klass):
        setattr(self, klass.__name__, DummyClass())
        for attr in klass.__dict__:
            try:
                args, kw = self.marked[klass.__dict__[attr]]
                setattr(getattr(self, klass.__name__), attr, task(*args,**kw)(getattr(klass, attr)))
            except KeyError:
                pass
        #reset for next class
        self.marked = {}
        return klass
9

我觉得你想做的事情用装饰器是行不通的(快速补充:特别是用方法的装饰器)。装饰器是在方法构造的时候被调用的,而这个时间点是在类被构造之前。你代码不工作的原因就是在装饰器被调用的时候,类还不存在。

jldupont的评论是个好主意:如果你想给类设置一个属性,应该装饰类本身或者使用元类。

补充一下:好吧,看到你的评论后,我想到一个可能适合你的两步解决方案。先用方法的装饰器给方法设置一个属性,然后用元类去查找带有这个属性的方法,并给类设置相应的属性:

def TaggingDecorator(method):
  "Decorate the method with an attribute to let the metaclass know it's there."
  method.my_attr = 'FOO BAR'
  return method # No need for a wrapper, we haven't changed
                # what method actually does; your mileage may vary

class TaggingMetaclass(type):
  "Metaclass to check for tags from TaggingDecorator and add them to the class."
  def __new__(cls, name, bases, dct):
    # Check for tagged members
    has_tag = False
    for member in dct.itervalues():
      if hasattr(member, 'my_attr'):
        has_tag = True
        break
    if has_tag:
      # Set the class attribute
      dct['my_attr'] = 'FOO BAR'
    # Now let 'type' actually allocate the class object and go on with life
    return type.__new__(cls, name, bases, dct)

就这样。使用方法如下:

class Foo(object):
  __metaclass__ = TaggingMetaclass
  pass

class Baz(Foo):
  "It's enough for a base class to have the right metaclass"
  @TaggingDecorator
  def Bar(self):
    pass

>> Baz.my_attr
'FOO BAR'

不过说实话?还是用 supported_methods = [...] 的方式吧。元类虽然很酷,但在你之后维护你代码的人可能会很讨厌你。

撰写回答