Есть ли чистый способ заставить декоратор вызывать метод экземпляра класса только во время создания экземпляра класса?

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()

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

3
Michael Mior 30 Июл 2010 в 17:06

4 ответа

Лучший ответ

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

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()
1
Michael Mior 30 Июл 2010 в 15:05

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

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
Daniel Kluev 30 Июл 2010 в 13:32

Как должны вести себя несколько экземпляров класса 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>}

Изменить . Чтобы избавиться от оператора 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
unutbu 30 Июл 2010 в 15:12

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

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

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

0
Katriel 30 Июл 2010 в 13:27