Подтвердить что ты не робот

Как вставить переменную в область с помощью декоратора в 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.

Есть ли способ сделать что-то вроде этого?

4b9b3361

Ответ 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()