Обновить декоратор - PullRequest
       5

Обновить декоратор

1 голос
/ 27 марта 2010

Я пытаюсь написать декоратор, который «обновляется» после вызова, но где обновление происходит только один раз после выхода из последней функции. Вот пример:

@auto_refresh
def a():
    print "In a"

@auto_refresh
def b():
    print "In b"
    a()

Если вызывается a(), я хочу, чтобы функция обновления запускалась после выхода из a(). Если вызывается b(), я хочу, чтобы функция обновления запускалась после выхода из b(), но не после a() при вызове b(). Вот пример класса, который делает это:

class auto_refresh(object):

def __init__(self, f):

    print "Initializing decorator"
    self.f = f

def __call__(self, *args, **kwargs):

    print "Before function"
    if 'refresh' in kwargs:
        refresh = kwargs.pop('refresh')
    else:
        refresh = False

    self.f(*args, **kwargs)

    print "After function"

    if refresh:
        print "Refreshing"

С этим декоратором, если я запускаю

b()
print '---'
b(refresh=True)
print '---'
b(refresh=False)

Я получаю следующий вывод:

Initializing decorator
Initializing decorator
Before function
In b
Before function
In a
After function
After function
---
Before function
In b
Before function
In a
After function
After function
Refreshing
---
Before function
In b
Before function
In a
After function
After function

Таким образом, когда вы пишете таким образом, отсутствие указания аргумента refresh означает, что по умолчанию установлено значение False. Кто-нибудь может придумать способ изменить это так, чтобы refresh было True, когда не указано? Изменение

refresh = False

до

refresh = True

в декораторе не работает:

Initializing decorator
Initializing decorator
Before function
In b
Before function
In a
After function
Refreshing
After function
Refreshing
---
Before function
In b
Before function
In a
After function
Refreshing
After function
Refreshing
---
Before function
In b
Before function
In a
After function
Refreshing
After function

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

Ответы [ 3 ]

5 голосов
/ 27 марта 2010

Хорошим примером использования локального хранилища потоков :

является подсчет количества вложений в поточно-ориентированном виде.
import threading
mydata = threading.local()
mydata.nesting = 0

class auto_refresh(object):

  def __init__(self, f):
    self.f = f

  def __call__(self, *args, **kwargs):
    mydata.nesting += 1
    try: return self.f(*args, **kwargs)
    finally:
      mydata.nesting -= 1
      if mydata.nesting == 0:
        print 'refreshing'

Если вы не заботитесь о многопоточности, пока ваша установка Python скомпилирована с включенной многопоточностью (и в наши дни почти все из них), это все равно будет работать нормально. Если вы боитесь нестандартной установки Python без многопоточности, измените оператор import на

try:
    import threading
except ImportError:
    import dummy_threading as threading

примерно так, как рекомендовано в документах (за исключением того, что документы используют своеобразное "личное" имя для результата импорта, и для этого нет реальной причины, поэтому я использую простое имя; - ).

1 голос
/ 27 марта 2010

Я думаю, что может быть проще поддерживать «число вложенных-обновлений», которое увеличивается перед каждым вызовом, украшенным обновлением, и уменьшается после (в блоке finally, чтобы количество не испортилось); запускать процедуру обновления всякий раз, когда счетчик достигает нуля.

0 голосов
/ 27 марта 2010

Мне не совсем ясно, для чего вы пытаетесь использовать этот дизайн, и из-за этого, является ли это хорошей идеей или нет. Подумайте, правильный ли это подход или нет.

Тем не менее, я думаю, что это делает то, что вы просили. Общий объект (в данном случае с именем auto_refresh) используется всеми «декорированными» методами, и этот объект хранит счетчик глубины стека вызовов.

Это не потокобезопасно.

class AutoRefresh(object):
    nesting = 0

    def __call__(self, f):
        def wrapper(*args, **kwargs):
            return self.proxied_call(f, args, kwargs)
        return wrapper

    def refresh(self):
        print 'refresh'

    def proxied_call(self, func, args, kwargs):
        self.nesting += 1
        result = func(*args, **kwargs)
        self.nesting -= 1
        if self.nesting == 0:
            self.refresh()
        return result

auto_refresh = AutoRefresh()

Проверено с:

@auto_refresh
def a():
    print "In a"

@auto_refresh
def b():
    print "In b"
    a()

a()
print '---'
b()

В результате:

In a
refresh
---
In b
In a
refresh
...