Вызов методов экземпляра Python в декораторах функций - PullRequest
3 голосов
/ 30 июля 2010

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

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

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

Ответы [ 4 ]

1 голос
/ 30 июля 2010

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

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 голосов
/ 30 июля 2010

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

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 голосов
/ 30 июля 2010

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...