Как вставить переменную в область с помощью декоратора в python
[Отказ от ответственности: может быть больше pythonic способов делать то, что я хочу сделать, но я хочу знать, как работает область обзора python]
Я пытаюсь найти способ сделать декоратор, который делает что-то вроде инъекции имени в область действия другой функции (так что имя не течет вне области декоратора). Например, если у меня есть функция, которая говорит, чтобы напечатать переменную с именем var
, которая не была определена, я бы хотел определить ее в декораторе, где он вызывается. Вот пример, который ломается:
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
var = value
res = f(*args, **kwargs)
return res
return inner_dec
return msg_decorator
@decorator_factory(c)
def msg_printer():
print var
msg_printer()
Я бы хотел, чтобы он печатал "Сообщение", но он дает:
NameError: global name 'var' is not defined
Определяется трассировка даже на wher var
:
<ipython-input-25-34b84bee70dc> in inner_dec(*args, **kwargs)
8 def inner_dec(*args, **kwargs):
9 var = value
---> 10 res = f(*args, **kwargs)
11 return res
12 return inner_dec
поэтому я не понимаю, почему он не может найти var
.
Есть ли способ сделать что-то вроде этого?
Ответы
Ответ 1
Вы не можете. Определенные имена (закрытия) определяются во время компиляции, вы не можете добавить больше во время выполнения.
Лучшее, что вы можете надеяться достичь, - это добавить глобальные имена, используя собственное глобальное пространство имен:
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
g = f.__globals__ # use f.func_globals for py < 2.6
sentinel = object()
oldvalue = g.get('var', sentinel)
g['var'] = value
try:
res = f(*args, **kwargs)
finally:
if oldvalue is sentinel:
del g['var']
else:
g['var'] = oldvalue
return res
return inner_dec
return msg_decorator
f.__globals__
- это глобальное пространство имен для завернутой функции, поэтому это работает, даже если декоратор живет в другом модуле. Если var
уже было определено как глобальное, оно заменяется новым значением, а после вызова функции восстанавливаются глобальные переменные.
Это работает, потому что любое имя в функции, которая не назначена и не найдена в окружении, помечена как глобальная.
Демо:
>>> c = 'Message'
>>> @decorator_factory(c)
... def msg_printer():
... print var
...
>>> msg_printer()
Message
>>> 'var' in globals()
False
Но вместо украшения я мог бы точно определить var
в глобальной области видимости напрямую.
Обратите внимание, что изменение глобальных переменных не является потокобезопасным, и любые переходные вызовы другим функциям в том же модуле также будут видеть этот же глобальный.
Ответ 2
Вы не можете. Python имеет лексическое определение. Это означает, что значение идентификатора определяется исключительно на основе областей, которые физически окружают его, когда вы смотрите на исходный код.
Ответ 3
Python лексически ограничен, поэтому я боюсь, что нет чистого способа делать то, что вы хотите, без каких-либо потенциально неприятных побочных эффектов. Я рекомендую просто передать var в функцию через декоратор.
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
res = f(value, *args, **kwargs)
return res
inner_dec.__name__ = f.__name__
inner_dec.__doc__ = f.__doc__
return inner_dec
return msg_decorator
@decorator_factory(c)
def msg_printer(var):
print var
msg_printer() # prints 'Message'
Ответ 4
Вот простая демонстрация использования декоратора для добавления переменной в область действия функции.
>>> def add_name(name):
... def inner(func):
... # Same as defining name within wrapped
... # function.
... func.func_globals['name'] = name
...
... # Simply returns wrapped function reference.
... return func
...
... return inner
...
>>> @add_name("Bobby")
... def say_hello():
... print "Hello %s!" % name
...
>>> print say_hello()
Hello Bobby!
>>>
Ответ 5
Существует чистый способ делать то, что вы хотите, не используя глобальную переменную. Если вы хотите быть безгосударственным и потокобезопасным, у вас действительно нет выбора.
Используйте переменную "kwargs".
c = 'Message'
def decorator_factory(value):
def msg_decorator(f):
def inner_dec(*args, **kwargs):
kwargs["var"] = value
res = f(*args, **kwargs)
return res
return inner_dec
return msg_decorator
@decorator_factory(c)
def msg_printer(*args, **kwargs):
print kwargs["var"]
msg_printer()