Как динамически изменять локальное пространство имен функции?

NB: этот вопрос предполагает Python 2.7.3.

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

То, что я имею в виду, будет выглядеть примерно так:

import os
from namespace_updater import update_locals

def somefunc(x, y, z):
    # ...
    # ...
    # this and that
    # ...
    # ...

    if os.environ.get('FROBNICATE'):
        from frobnitz import frobnicate
        update_locals(frobnicate(locals()))

    #
    # life goes on, possibly with duly frobnicated local variables...
    # ...
    # ...
    # ...

Спасибо!


PS: Ниже приведены подходы, которые не работают.

Самый наивный подход к этому был бы примерно таким:

locals().update(new_locals(locals())

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

Далее по шкале наивности что-то вроде

for k, v in new_locals(locals()).items():
    exec ('%s = v' % k)

AFAICT, такой код не может быть "в стороне" (т. е. он должен находиться в теле функции), что не идеально. Но настоящим препятствием является то, что exec ('%s = v' % k) хак может привести к некоторым причудливым ошибкам.

Когда я пишу «причудливые ошибки», я имею в виду «ошибки, которые кажутся странными для кого-то с таким слабым пониманием exec ('%s = v' % k), как у меня». Насколько слабо я понимаю этот хак? Чтобы ответить на этот вопрос, рассмотрим сценарий ниже. Он имеет три варианта: (1) точно такой, как показано; (2) после удаления начального # строки 18; (3) после удаления первых # в обеих строках 15 и 18 (т.е. для этого варианта код не закомментирован). Я не мог предсказать поведение вариантов (2) и (3) этого скрипта. Я даже не смог бы с уверенностью более 50% предсказать поведение варианта (1). Вот как слабо я понимаю exec ('%s = v' % k) хак. Если вы не можете уверенно и правильно предсказать, как поведут себя три варианта этого скрипта (под Python 2.7), можно с уверенностью сказать, что ваше понимание ситуации примерно такое же слабое, как и мое, и вам, вероятно, тоже следует держаться подальше от exec ('%s = v' % k).

x = 'global x'                            # 01
y = 'global y'                            # 02
def main():                               # 03
    x = 'local x'                         # 04
    y = 'local y'                         # 05
    run(locals())                         # 06
    print 'OK'                            # 07
    return 0                              # 08
                                          # 09
def run(namespace):                       # 10
    global y                              # 11
    print locals().keys()                 # 12
    for k, v in namespace.items():        # 13
        print '%s <- %r' % (k, v)         # 14
        exec ('%s = v' % k) #in locals()  # 15
    print locals().keys()                 # 16
    x = x                                 # 17
    #z = lambda: k                        # 18
    print x                               # 19
    print y                               # 20
                                          # 21
exit(main())                              # 22

person kjo    schedule 07.05.2012    source источник
comment
Вопрос stackoverflow.com/questions/1142068 имеет название, похожее на мой, но я думаю, что эти два вопроса совершенно разные по содержанию; более того, я не нахожу ни одного из ответов, данных на этот вопрос, ответом на мой.   -  person kjo    schedule 08.05.2012
comment
Есть какой-то ужасный хак с использованием ctypes, который делает это возможным, но я согласен с Никласом: это почти наверняка не решение какой-либо проблемы.   -  person Thomas K    schedule 08.05.2012
comment
@NiklasB.: Мне жаль, что я так колю, но ваш комментарий ужасно снисходителен. Я программирую ›25 лет. Я знаю, что я делаю.   -  person kjo    schedule 08.05.2012
comment
@kjo: Извините, я думаю, что ошибся. Тем не менее, моя точка зрения остается неизменной: Python не имеет функции, которую вы ищете, и на то есть веские причины. Вероятно, вы можете обойти это (в конце концов, вы можете создавать любой код динамически и exec), но это не будет хорошим решением и, возможно, даже специфичным для реализации. Хорошее решение будет учитывать проблему, которую вы не описали. По моему опыту, SO не очень хорошо подходит для таких вопросов, поэтому, возможно, это действительно еще один случай Проблема XY.   -  person Niklas B.    schedule 08.05.2012
comment
Например, что удерживает вас от сохранения ассоциации «имя-значение» внутри словаря или объекта, а не в локальной области имен? Вы пытаетесь реализовать какой-то встроенный язык?   -  person Niklas B.    schedule 08.05.2012
comment
Чего я не понимаю, так это почему ветка не может оставаться строго сосредоточенной на том, что я спросил. Если вам не нравится то, что я хотел бы сделать, просто идите дальше, или в самом списке дайте ссылку на подробное объяснение, почему вы считаете это такой плохой идеей. Бесконечное трепание на нем не изменит моего мнения. Как я уже говорил вам, как бы вам ни было трудно в это поверить, я не идиот. Я достаточно долго думал о том, о чем спрашиваю, и несколько кратких замечаний не изменят моего мнения.   -  person kjo    schedule 08.05.2012
comment
что, если вместо этого у нас есть метод класса/экземпляра, и мы все еще хотим изменить его пространство имен локальной переменной?   -  person Charlie Parker    schedule 23.06.2017
comment
есть ли версия этого вопроса, которая работает для python 3 или выше?   -  person Charlie Parker    schedule 17.10.2017


Ответы (3)


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

def process(**kw):
  mycode = """\
print 'Value of foo is %s' % (foo,)
print 'Value of bar is %s' % (bar,)
"""
  exec mycode in kw

vars = {'foo': 2, 'bar': 3}
process(**vars)

При таком подходе у вас есть хоть какая-то защита от атак с внедрением кода. Словарь, содержащий «локальные переменные» кода, указывается явно, поэтому вы полностью контролируете, каким будет пространство переменных при выполнении инструкции exec. Вам не нужно взламывать внутренности объектов-функций или что-то в этом роде.

Я знаю, что модуль декоратора использует exec в реализации @decorator для управления именами аргументов в динамически создаваемых функциях. , и могут быть другие распространенные модули, которые его используют. Но я был только в одной ситуации, когда exec был явным преимуществом над альтернативами в Python, и в одной ситуации eval.

Я не вижу такой ситуации в вашем вопросе. Если mycode из вышеприведенного не нужно делать что-то действительно необычное, например, создать функцию с именами аргументов, указанными в kw, скорее всего, вы можете просто написать код и, возможно, использовать locals() в крайнем случае.

def process(**kw):
  print 'Value of foo is %s' % (kw['foo'],)
  print 'Value of bar is %s' % (kw['bar'],)

process(foo=2, bar=3)
person wberry    schedule 07.05.2012
comment
Просто примечание, вы также можете просто сделать 'foo %s bar' % 'some', вам не нужен одноэлементный кортеж :) - person Niklas B.; 08.05.2012

Может быть что-то вроде этого

def foo():
    print(x)

foo.__globals__["x"] = "Hello Python"

foo()

к сожалению, это не работает в теле функции, если переменная была определена

def foo(flag):
    x = "Hello World"
    if flag:
        foo.__globals__["x"] = "Hello Python"

    print(x)

печатает Hello World в обоих случаях flag имеет значение True или False

person atomAltera    schedule 07.05.2012
comment
Это не работает, потому что вы изменяете глобальное пространство имен, а не локальное, а локальные переменные имеют приоритет над глобальными. - person Thomas K; 08.05.2012
comment
что, если вместо этого у нас есть метод класса/экземпляра, и мы все еще хотим изменить его пространство имен локальной переменной? - person Charlie Parker; 23.06.2017

Не уверен, что это возможно только с внешней функцией. Я создал фрагмент:

def get_module_prefix(mod, localsDict):
    for name, value in localsDict.iteritems():
        if value == mod:
            return name
    raise Exception("Not found")

def get_new_locals(mod, localsDict):
    modulePrefix = get_module_prefix(mod, localsDict)
    stmts = []
    for name in dir(mod):
        if name.startswith('_'):
            continue
        if name not in localsDict:
            continue
        stmts.append("%s = %s.%s" % (name, modulePrefix, name))
    return "\n".join(stmts)

def func(someName):
    from some.dotted.prefix import some.dotted.name
    #here we update locals
    exec(get_new_locals(some.dotted.name, "some.dotted.name", locals()))
    print locals()
    print someName # value taken from aModule instead of parameter value


func(5)

где:

  • get_module_prefix используется для поиска имени, под которым импортируется модуль,
  • get_new_locals возвращает операторы присваивания, которые можно использовать для обновления локальных переменных,

Фактическое обновление локальных переменных выполняется в строке exec(get_new_locals(some.dotted.name, locals())), где мы просто выполняем операторы присваивания, в которых мы присваиваем значения из модуля локальным переменным.

Я не уверен, что это то, что вы на самом деле имеете в виду.

person uhz    schedule 07.05.2012
comment
Это потерпит неудачу так же, как и собственная попытка OP, когда вы вложите функцию внутри func. - person Niklas B.; 08.05.2012