Внедрение переменных в область вызова?

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

Ответы

Ответ 1

По правилам Python вы не можете изменить свой вызывающий locals; в текущих реализациях, если вы попробуете (например, с черной магией Анураг предлагает), вы не получите исключение (хотя я бы хотел добавить эту проверку ошибок в какую-то будущую версию), но она будет по сути недействительна, если ваш вызывающий функция (не если ваш вызывающий является модулем верхнего уровня модуля) - фактические локальные переменные вызывающего абонента фактически не будут затронуты. Это означает, что вызывающий locals явно передан или извлечен черной магией: их все равно нужно рассматривать как только для чтения, если ваш код должен иметь какое-либо здравомыслие.

Скорее, вы могли бы передать вызывающий код в явном, реальном, нормальном dict (который может быть инициализирован из locals(), если вы хотите), и все изменения, которые ваш код делает в этом dict, все равно будут использоваться для использования вызывающего абонента - просто не как "новые барные имена" в области локального вызова, конечно, но функциональность одинакова, нужно ли вызывающему абоненту использовать x['foo'] или x.foo или (как вы предпочитаете) просто barename foo.

BTW, чтобы использовать синтаксис атрибутного доступа, а не синтаксис индексации, вы можете сделать:

class Bunch(object): pass

...

# caller code
b = Bunch()
thefun(b)
print b.foo

...

# called function
def thefun(b):
  b.foo = 23

Это также охватывает крошечный вариант, в котором thefun хочет работать с синтаксисом индексирования dict (например, его тело b['foo'] = 23 вместо b.foo = 23): в этом случае вызывающему нужно просто используйте thefun(vars(b)) вместо обычного thefun(b), но после этого он может продолжать работать с синтаксисом доступа b.foo.

Ответ 2

Проверьте модуль проверки, он используется minimock, чтобы высмеять область вызова.

Этот код должен делать то, что вы хотите точно:

import inspect
def mess_with_caller():
    stack = inspect.stack()
    try:
        locals_ = stack[1][0].f_locals
    finally:
        del stack
    locals_['my_new_function'] = lambda : 'yaaaaaay'


mess_with_caller()
print my_new_function()
>>> Output: 'yaaaaaay'

Ответ 3

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

Один из способов сделать это - передать объект и очистить внутреннее состояние объекта, когда он будет выполнен с его помощью.

class Obj(object):
    def __init__(self, state):
        self.state = state

def f(o):
    # Do stuff with o.state
    del o.state