2010-10-07 3 views
4

Это не работает:Python - декоратор - пытается получить доступ к родительскому классу метода

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 

декораторов как фильм Inception; чем больше уровней у вас, тем больше они запутываются. Я пытаюсь получить доступ к классу, который определяет метод (во время определения), чтобы я мог установить атрибут (или изменить атрибут) класса.

Version 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 

Точка положить мой сломанный код выше, когда я уже знаю, почему она сломана, что он передает то, что я пытаюсь сделать.

+0

и создание метакласса не помогло бы? – jldupont

ответ

7

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

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

EDIT: хорошо, увидев ваш комментарий, я могу придумать решение из двух частей, которое может сработать для вас. Используйте декоратор метода, чтобы установить атрибут метода, а затем использовать метакласс для поиска методов с этим атрибутом и установить соответствующий атрибут класса:

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

+0

спасибо. Я начну регенерировать волосы, которые я потерял за последний час. Как бы я это сделал? Я не понимаю мета-классы, но я также не понимаю, как украшение класса может помочь с тем, что я делаю.Чтобы уточнить, мне нужно иметь возможность запускать метод для экземпляров класса 'self.supports_method (method_name_string)', чтобы узнать, поддерживаются ли методы. Я пытаюсь сделать его «классным», хотя без подклассов необходимо объявить атрибут 'supported_methods = ['method_one', 'method_two']' для каждого класса. – orokusaki

+0

@orokusaki: см. Редактирование. –

+0

отлично - я только что прочитал последние 30 минут на IBM и другие вопросы о метаклассах, и я рад, что это сделал. Я пришел к выводу (основанный на использовании 'func.is_hook' в SO-ответе), что мне нужно будет отметить методы для включения в мой декоратор (если бы я не хотел доверять чистой метамагии, чтобы понять, что я хотел через другие конвенции). Я собирался повесить себя после того, как понял, сколько еще «выяснил», что у меня осталось на весь день. Это то, что я обновил на странице :) Вы просто спасли остальную часть моих волос. Спасибо, Питер. – orokusaki

2

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

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

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 
Смежные вопросы