2013-06-03 5 views
4

У меня есть код в декораторе, который я хочу запустить только один раз. Многие другие функции (утилита и прочее) будут вызваны позже по строке, и я хочу, чтобы другие функции, которые могут иметь этот декоратор, случайно не используются в гнезде вызовов функций.Предотвращение вставки функции (или декоратора)

Я также хочу проверить, в любой момент, был ли текущий код завернут в декоратор или нет.

Я написал это, но я просто хотел узнать, может ли кто-нибудь еще подумать о лучшем/более элегантном решении, чем проверять уникальное имя функции (надеюсь,!) В стеке.

import inspect 

def my_special_wrapper(fn): 
    def my_special_wrapper(*args, **kwargs): 
     """ Do some magic, only once! """ 
     # Check we've not done this before 
     for frame in inspect.stack()[1:]: # get stack, ignoring current! 
      if frame[3] == 'my_special_wrapper': 
       raise StandardError('Special wrapper cannot be nested') 
     # Do magic then call fn 
     # ... 
     fn(*args, **kwargs) 
    return my_special_wrapper 

def within_special_wrapper(): 
    """ Helper to check that the function has been specially wrapped """ 
    for frame in inspect.stack(): 
     if frame[3] == 'my_special_wrapper': 
      return True 
    return False 

@my_special_wrapper 
def foo(): 
    print within_special_wrapper() 
    bar() 
    print 'Success!' 

@my_special_wrapper  
def bar(): 
    pass 

foo() 
+0

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

+1

Я не думаю, что это должно быть. Обертка добавляет логику транзакции базы данных, обрабатывает запуск/фиксацию и повторные попытки блокировки и т. Д. Использование этого в качестве декоратора идеально. Транзакции не могут быть вложенными, поэтому мне нужно выбросить ошибку, если это произойдет. –

+0

Достаточно справедливо, это звучит достаточно обоснованно. –

ответ

3

Вот пример использования глобального для решения этой задачи - в том, что я считаю, является относительно безопасным способом:

from contextlib import contextmanager 
from functools import wraps 

_within_special_context = False 

@contextmanager 
def flag(): 
    global _within_special_context 
    _within_special_context = True 
    try: 
     yield 
    finally: 
     _within_special_context = False 


#I'd argue this would be best replaced by just checking the variable, but 
#included for completeness. 
def within_special_wrapper(): 
    return _within_special_context 


def my_special_wrapper(f): 
    @wraps(f) 
    def internal(*args, **kwargs): 
     if not _within_special_context: 
      with flag(): 
       ... 
       f(*args, **kwargs) 
     else: 
      raise Exception("No nested calls!") 
    return internal 

@my_special_wrapper 
def foo(): 
    print(within_special_wrapper()) 
    bar() 
    print('Success!') 

@my_special_wrapper 
def bar(): 
    pass 

foo() 

Каких результатов в:

True 
Traceback (most recent call last): 
    File "/Users/gareth/Development/so/test.py", line 39, in <module> 
    foo() 
    File "/Users/gareth/Development/so/test.py", line 24, in internal 
    f(*args, **kwargs) 
    File "/Users/gareth/Development/so/test.py", line 32, in foo 
    bar() 
    File "/Users/gareth/Development/so/test.py", line 26, in internal 
    raise Exception("No nested calls!") 
Exception: No nested calls! 

Использование контекста менеджер гарантирует, что переменная не установлена. Вы можете просто использовать try/finally, но если вы хотите изменить поведение для разных ситуаций, менеджер контекста может быть сделан гибким и многоразовым.

+0

Да, я думаю, что это путь вперед, мне это нравится! –

+1

Да. В этом случае использование глобалов имеет смысл, поскольку вы хотите * блокировать этот материал * глобально *. Очевидно, что вы можете использовать атрибуты классов (например) в других случаях, когда глобальное состояние не подходит. –

+0

Использование контекстного менеджера, безусловно, лучший способ. Что касается использования функции - это то, что может быть полезно в будущем, и делает менее вероятным случайное установление/снятие флажка. – Marcin

2

Очевидное решение состоит в special_wrapper установить глобальный флаг, и просто пропустить его магию, если флаг установлен.

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

Его нельзя устанавливать в глобальном масштабе. Функция может установить флаг как на себя, так и на любой объект или класс, если ничто другое не коснется его.

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

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

Обновление 2: Я вижу, что вы делаете это для управления транзакциями. Вероятно, уже есть библиотеки, которые это делают. Я настоятельно рекомендую вам хотя бы посмотреть на их код.

+0

Я только что отредактировал вопрос, чтобы добавить другое, что мне было нужно (и забыл добавить сначала, извините!), И это была функция 'inside_special_wrapper', которая может определить, содержится ли текущий код в этой специальной оболочке. Будет ли это по-прежнему осуществимо с глобальным флагом? Я думаю, это и мешало мне преследовать глобальную переменную. –

+0

@MichaelWaterfall Просто проверьте значение переменной. –

+0

Хм, это правда. Я предполагаю, что я имею в виду потенциальных проблем параллелизма, но с Django и gunicorn, я думаю, что это спорный вопрос. –

0

В то время как мое решение технически работает, оно требует ручного сброса декоратора, но вы можете очень хорошо изменить такие вещи, чтобы внешняя функция была вместо класса (с экземплярами, являющимися обертками декорированных функций, переданных ей в __init__) и имеют reset(), вызываемый в __exit__(), который затем позволит вам использовать оператор with для создания декоратора, который будет использоваться только один раз в контексте. Также обратите внимание, что для него требуется Python 3 из-за ключевого слова nonlocal, но это легко адаптировать к 2.7 с помощью dict вместо переменной флага.

def once_usable(decorator): 
    "Apply this decorator function to the decorator you want to be usable only once until it is reset." 

    def outer_wrapper(): 
     flag = False 

     def inner_wrapper(*args, **kwargs): 
      nonlocal flag 
      if not flag: 
       flag = True 
       return decorator(*args, **kwargs) 
      else: 
       print("Decorator currently unusable.") # raising an Error also works 

     def decorator_reset(): 
      nonlocal flag 
      flag = False 

     return (inner_wrapper, decorator_reset) 

    return outer_wrapper() 

Тестирование:

>>> def a(aa): 
    return aa*2 

>>> def b(bb): 
    def wrapper(*args, **kwargs): 
     print("Decorated.") 
     return bb(*args, **kwargs) 

    return wrapper 

>>> dec, reset = once_usable(b) 
>>> aa = dec(a) 
>>> aa(22) 
Decorated. 
44 
>>> aaa = dec(a) 
Decorator currently unusable. 
>>> reset() 
>>> aaa = dec(a) 
>>> aaa(11) 
Decorated. 
22 
+0

Чтобы вручную вызвать 'reset()' тип defies point. –

+0

@Lattyware Достаточно, я только что придумал это первым и не хотел переписывать его как класс, чтобы правильно реализовать менеджер контекста. Тем не менее, я сделал заметку о возможности использования менеджера контекста для этого в своем ответе. – JAB

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