2010-07-30 5 views
3

Есть ли чистый способ, чтобы декоратор вызывал метод экземпляра в классе только в момент создания экземпляра класса?Вызов методов экземпляра Python в декораторах функций

class C: 
    def instance_method(self): 
     print('Method called') 

    def decorator(f): 
     print('Locals in decorator %s ' % locals()) 
     def wrap(f): 
      print('Locals in wrapper %s' % locals()) 
      self.instance_method() 
      return f 
     return wrap 

    @decorator 
    def function(self): 
     pass 

c = C() 
c.function() 

Я знаю, что это не работает, потому что self не определена в точке decorator называется (так как он не вызываются как метод экземпляра, пока нет доступной ссылки на класс). Затем я пришел к этому решению:

class C: 
    def instance_method(self): 
     print('Method called') 

    def decorator(): 
     print('Locals in decorator %s ' % locals()) 
     def wrap(f): 
      def wrapped_f(*args): 
       print('Locals in wrapper %s' % locals()) 
       args[0].instance_method() 
       return f 
      return wrapped_f 
     return wrap 

    @decorator() 
    def function(self): 
     pass 

c = C() 
c.function() 

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

class C: 
    def instance_method(self): 
     print('Method called') 
def decorator(called=[]): 
    print('Locals in decorator %s ' % locals()) 
    def wrap(f): 
     def wrapped_f(*args): 
      print('Locals in wrapper %s' % locals()) 
      if f.__name__ not in called: 
       called.append(f.__name__) 
       args[0].instance_method() 
      return f 
     return wrapped_f 
    return wrap 

@decorator() 
def function(self): 
    pass 

c = C() 
c.function() 
c.function() 

Теперь функция только вызывается один раз, но мне не нравится тот факт, что эта проверка должна происходить каждый раз, когда функция вызывается. Я предполагаю, что нет никакого способа обойти это, но если у кого есть какие-то предложения, я бы хотел их услышать! Спасибо :)

+1

Если вы используете (AFAIK) python 2.5+, вы должны, вероятно, создать класс подкласса: 'class C (object):' для получения преимуществ классов нового стиля. –

+0

Ваши имена ошибочны. То, что вы называете «декоратором», на самом деле является декоратором * factory *, и то, что вы называете 'wrap', является декоратором. Кроме того, использование 'called = []' несколько обманчиво; хотя в этом случае это правильно. – katrielalex

+0

@katriealex Достаточно честный. Недавно я познакомился с этим синтаксисом для декораторов. У меня был этот пример внутри другой базы кода, поэтому я просто быстро переименовал функции для очистки вещей. Спасибо за разъяснение хотя :) –

ответ

1

Я придумал это в качестве возможного альтернативного решения. Мне нравится, потому что есть только один вызов, который происходит, когда функция определена, и одна, когда экземпляр класса создается. Единственный недостаток - это крошечный бит дополнительного потребления памяти для атрибута функции.

from types import FunctionType 

class C: 
    def __init__(self): 
     for name,f in C.__dict__.iteritems(): 
      if type(f) == FunctionType and hasattr(f, 'setup'): 
        self.instance_method() 

    def instance_method(self): 
     print('Method called') 

    def decorator(f): 
     setattr(f, 'setup', True) 
     return f 

    @decorator 
    def function(self): 
     pass 

c = C() 
c.function() 
c.function() 
+0

Извинения тем, кто нашел время для обработки ответов. Очень признателен. Тем не менее, я думаю, что решение, которое я, наконец, придумал, соответствует моим потребностям лучше всего. –

0

Я думаю, что вы просите что-то принципиально невозможное. Декоратор будет создан одновременно с классом , но метод экземпляра не существует до тех пор, пока экземпляр не сделает это, что позже. Поэтому декоратор не может обрабатывать специфичные для экземпляра функции.

Другой способ подумать об этом заключается в том, что декоратор является функтором: он преобразует функции в другие функции. Но он ничего не говорит о аргументах этих функций; он работает на более высоком уровне, чем это. Поэтому вызов метода экземпляра по аргументу function не является чем-то, что должен сделать декоратор; это то, что должно быть сделано function.

Способ обойти это обязательно хакерский. Ваш метод выглядит нормально, так как взломы идут.

+0

Да, я знаю, как декораторы определены. Мне было просто любопытно, если кто-то испытал эту проблему раньше и, возможно, придумал лучший взлом, чем я. –

0

Это может быть достигнуто с использованием callables в качестве декораторов.

class ADecorator(object): 
    func = None 
    def __new__(cls, func): 
     dec = object.__new__(cls) 
     dec.__init__(func) 
     def wrapper(*args, **kw): 
      return dec(*args, **kw) 
     return wrapper 

    def __init__(self, func, *args, **kw): 
     self.func = func 
     self.act = self.do_first 

    def do_rest(self, *args, **kw): 
     pass 

    def do_first(self, *args, **kw): 
     args[0].a() 
     self.act = self.do_rest 

    def __call__(self, *args, **kw): 
     return self.act(*args, **kw) 

class A(object): 
    def a(self): 
     print "Original A.a()" 

    @ADecorator 
    def function(self): 
     pass 


a = A() 
a.function() 
a.function() 
+0

Это работает, хотя ненужный вызов функции все равно выполняется каждый раз, когда оформленная функция выполняется (до 'do_rest'). –

+0

Я не думаю, что его можно устранить как проверки, так и вызовы в общем случае. Однако, если вы знаете имя метода, вы можете повторно привязать его к функции do-nothing. И имя метода можно получить из объекта 'func', который получает наш декоратор. Итак, вы можете делать args [0] .__ setattr __ (self.func .__ name__, do_nothing) в do_first(). Очевидно, вам нужно было бы объявить какое-нибудь do_nothing где-нибудь или использовать для этого встроенный. Однако это еще более «хакерский» и время, которое оно экономит, не стоит возможных побочных эффектов. –

0

Как должно выглядеть несколько экземпляров класса C? Должно ли instance_method вызываться только один раз, независимо от того, какой экземпляр вызывает function? Или каждый экземпляр вызовет instance_method один раз?

Ваш аргумент called=[] по умолчанию заставляет декоратора помнить, что было вызвано что-то с именем строки function. Что делать, если decorator используется для двух разных классов, у которых есть метод с именем function? Тогда

c=C() 
d=D() 
c.function() 
d.function() 

будет вызывать только c.instance_method и предотвратить d.instance_method от вызывался. Странно и, вероятно, не то, что вы хотите.

Ниже я использую self._instance_method_called для записи, если был вызван self.instance_method. Это делает каждый экземпляр C называть instance_method не более одного раза.

Если вы хотите instance_method называться не более одного раза, независимо от того, какого экземпляра C вызовов function, то просто определить _instance_method_called как класс атрибут вместо атрибут экземпляра.

def decorator(): 
    print('Locals in decorator %s ' % locals()) 
    def wrap(f): 
     def wrapped(self,*args): 
      print('Locals in wrapper %s' % locals())    
      if not self._instance_method_called: 
       self.instance_method() 
       self._instance_method_called=True 
      return f 
     return wrapped 
    return wrap 

class C: 
    def __init__(self): 
     self._instance_method_called=False 
    def instance_method(self): print('Method called') 
    @decorator() 
    def function(self): 
     pass 

c = C() 
# Locals in decorator {} 
c.function() 
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1aec>, 'args':(), 'f': <function function at 0xb76eed14>} 
# Method called 
c.function() 
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1aec>, 'args':(), 'f': <function function at 0xb76eed14>} 

d = C() 
d.function() 
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1bcc>, 'args':(), 'f': <function function at 0xb76eed14>} 
# Method called 
d.function() 
# Locals in wrapper {'self': <__main__.C instance at 0xb76f1bcc>, 'args':(), 'f': <function function at 0xb76eed14>} 

Edit: Чтобы избавиться от if заявления:

def decorator(): 
    print('Locals in decorator %s ' % locals()) 
    def wrap(f): 
     def rest(self,*args): 
      print('Locals in wrapper %s' % locals()) 
      return f 
     def first(self,*args): 
      print('Locals in wrapper %s' % locals())    
      self.instance_method() 
      setattr(self.__class__,f.func_name,rest) 
      return f 
     return first 
    return wrap 

class C: 
    def instance_method(self): print('Method called') 
    @decorator() 
    def function(self): 
     pass 
+0

Правда. Однако не отвечает на мой вопрос. –

+0

О, вы имеете в виду удаление инструкции 'if'? Ну, я знаю способ, если вы хотите, чтобы 'instance_method' вызывался только один раз, независимо от того, какой экземпляр вызывает' c.function() '. Я не могу придумать способ сделать это без инструкции 'if', если вы хотите, чтобы экземпляры' C' вели себя индивидуально. – unutbu

+0

Я редактировал свое сообщение, чтобы показать, что я имею в виду. – unutbu

Смежные вопросы