Python - декоратор - пытается получить доступ к родительскому классу метода - PullRequest
4 голосов
/ 08 октября 2010

Это не работает:

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 ]

7 голосов
/ 08 октября 2010

Я не думаю, что вы можете делать то, что вы хотите делать с декоратором (быстрое редактирование: в любом случае, с декоратором метода).Декоратор вызывается, когда метод создается, что составляет до класса.Причина, по которой ваш код не работает, состоит в том, что класс не существует, когда вызывается декоратор.

Комментарий jldupont - это путь: если вы хотите установить атрибут класса , вы должны либо украсить класс, либо использовать метакласс.

РЕДАКТИРОВАТЬ: хорошо, увидев ваш комментарий, я могу подумать о решении из двух частей, которое может работать для вас.Используйте декоратор метода для установки атрибута метода , а затем используйте метакласс для поиска методов с этим атрибутом и установите соответствующий атрибут class :

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 = [...].Метаклассы - это круто, но люди, которые должны поддерживать ваш код после вас, вероятно, будут вас ненавидеть.

2 голосов
/ 24 февраля 2011

Вместо использования метакласса, в Python 2.6+ вы должны использовать декоратор классов. Вы можете обернуть функцию и декораторы класса как методы класса, как в этом реальном примере.

Я использую этот пример с djcelery; Важными аспектами этой проблемы являются метод «task» и строка «args, kw = self.marked [klass. dict [attr]]», которая неявно проверяет наличие «klass. dict *». 1006 * [attr] в self.marked ". Если вы хотите использовать @ methodtasks.task вместо @ methodtasks.task () в качестве декоратора, вы можете удалить вложенный def и использовать набор вместо dict для self.marked. Использование self.marked вместо установки атрибута маркировки для функции, как это сделал другой ответ, позволяет этому работать для методов классов и статических методов, которые, поскольку они используют слоты, не позволяют устанавливать произвольные атрибуты. Недостатком такого способа является то, что декоратор функций ДОЛЖЕН идти выше других декораторов, а декоратор класса ДОЛЖЕН идти ниже, чтобы функции не модифицировались / переворачивались между одним и другим.

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
...