Любой способ изменить словарь местных жителей?

locals - встроенная функция, которая возвращает словарь локальных значений. В документации сказано:

Предупреждение

Содержание этого словаря не должно изменяться; изменения не могут повлиять на значения локальных переменных, используемых интерпретатором.

К сожалению, у exec такая же проблема в Python 3.0. Есть ли способ обойти это?

Пример использования

Рассмотреть возможность:

@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

зависимости хранит строки, указанные в его аргументах, в списке test.dependences. Эти строки являются ключами в словаре d. Я хотел бы иметь возможность писать put_into_locals, чтобы мы могли извлекать значения из d и помещать их в локальные переменные. Это возможно?


person Casebash    schedule 20.09.2009    source источник
comment
Ссылка на соответствующую документацию: docs.python.org/2/library/functions. html # locals   -  person Ceasar Bautista    schedule 05.12.2014
comment
почему test. dependencies = ["a", "b", "c", "d", "e", "f"] работает, а затем украшает назначение, которое я написал выше вашей test() функции?   -  person Charlie Parker    schedule 23.06.2017
comment
удалось обновить / изменить локальные жители или нет?   -  person Charlie Parker    schedule 17.10.2017
comment
есть ли способ заставить его работать для Python 3 или более?   -  person Charlie Parker    schedule 17.10.2017


Ответы (5)


Я только что протестировал exec, и он работает на Python 2.6.2.

>>> def test():
...     exec "a = 5"
...     print a
...
>>> test()
5

Если вы используете Python 3.x, он больше не работает, потому что локальные переменные оптимизируются как массив во время выполнения вместо использования словаря.

Когда Python обнаруживает «оператор exec», он заставляет Python переключить локальное хранилище с массива на словарь. Однако, поскольку «exec» является функцией в Python 3.x, компилятор не может сделать это различие, поскольку пользователь мог сделать что-то вроде «exec = 123».

http://bugs.python.org/issue4831

Изменение локальных переменных функции на лету невозможно без нескольких последствий: обычно локальные переменные функции хранятся не в словаре, а в массиве, индексы которого определяются во время компиляции из известных локалей. Это конфликтует, по крайней мере, с новыми локальными жителями, добавленными exec. Старый оператор exec обошел это, потому что компилятор знал, что если в функции произойдет exec без аргументов globals / locals, это пространство имен будет «неоптимизированным», то есть не будет использовать массив locals. Поскольку exec () теперь является нормальной функцией, компилятор не знает, к чему может быть привязан «exec», и поэтому не может обрабатывать его специально.

person Unknown    schedule 20.09.2009
comment
Я думаю, это убедительно, что это просто невозможно. - person Casebash; 20.09.2009
comment
@Casebash, это возможно, просто требуется взлом байтового кода или Python 2.x - person Unknown; 20.09.2009
comment
@Casebash: возможно, тебе не захочется задерживать дыхание. Байт-коды Python не очень хорошо документированы. - person Unknown; 20.09.2009
comment
Я, наверное, когда-нибудь посмотрю на это сам. Банкомат, я действительно не собираюсь получать от него достаточно полезности, чтобы оправдать усилия - person Casebash; 21.09.2009
comment
Проблема не в том, что Интерпретатор без нужды откажется от оптимизации после exec=123; дело в том, что он будет доверять невинно выглядящему print("hello=world") даже после print=eval. - person tiwo; 06.02.2013
comment
есть ли встроенный способ без использования exec? - person Charlie Parker; 17.10.2017
comment
означает ли это, что это не может работать даже с оператором exec? потому что я пробовал это с exec, и хотя переменная существует, она не может найти ее позже в сценарии ... - person Charlie Parker; 17.10.2017

Локальные переменные изменяются операторами присваивания.

Если у вас есть ключи словаря, которые являются строками, пожалуйста, не делайте их также локальными переменными - просто используйте их как ключи словаря.

Если вам абсолютно необходимы локальные переменные, сделайте это.

def aFunction( a, b, c, d, e, f ):
    # use a, b, c, d, e and f as local variables

aFunction( **someDictWithKeys_a_b_c_d_e_f )

Это заполнит некоторые локальные переменные из вашего словаря без каких-либо магических действий.

person S.Lott    schedule 20.09.2009
comment
именно то, о чем я думал; вы также можете динамически создавать функцию; см. справку (types.FunctionType) - person gatoatigrado; 05.10.2009
comment
Это интересная идея. Однако есть много приложений, в которых словарь фактически содержит много других переменных (которые не нужны aFunction()), что делает текущее определение aFunction() прерывающимся. Полезное обобщение: aFunction(a, b, c, d, e, f, **kwargs). - person Eric O Lebigot; 18.07.2010
comment
@EOL: Дополнительные переменные параметра прерывают функцию? Трудно представить. Несколько дополнительных переменных должны быть ... ну ... просто переменными. Функция, которая не работает из-за нескольких дополнительных переменных, имеет очень плохой дизайн. Лучше бы эту функцию поправить. - person S.Lott; 18.07.2010
comment
@S. Лотт: Позвольте мне перефразировать свою точку зрения: что ломается, так это наличие подписи def aFunction(a, b, c, d, e, f), когда someDictWithKeys_a_b_c_d_e_f содержит больше ключей, чем эти несколько переменных, что является типичной ситуацией при выполнении сложных научных вычислений (весь расчет использует больше переменных, чем большинство функций, которые он вызывает) . Как я уже отмечал, def aFunction(a, b, c, d, e, f, **kwargs) - удобный способ разрешения этой ситуации. - person Eric O Lebigot; 18.07.2010
comment
@EOL: Я не понимаю, как это ломается. Есть просто лишние переменные, которые не используются. Как это сломано? - person S.Lott; 19.07.2010
comment
@S. Лотт: просто запустите код своего ответа (за который я проголосовал) с aFunction(**{'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6}), и это точно покажет, почему мое замечание может быть полезно читателям StackOverflow. В реальных научных расчетах словари, как правило, содержат больше переменных, чем то, что передается отдельно каждой вызываемой функции. - person Eric O Lebigot; 20.07.2010
comment
@EOL: Обычно? Это просто плохой дизайн. Срывается? Это просто плохой дизайн. Вы полагаете, что плохой дизайн можно терпеть? Это ваше определение перерывов? есть дизайн, который имеет фатальные изъяны? Я думаю, что разрывы - неправильное слово. Я думаю, что вы говорите о поддержке правильной отладки. Я не могу серьезно представить себе программное обеспечение, написанное настолько плохо, что функции обычно имеют неверный набор аргументов. - person S.Lott; 20.07.2010
comment
@S. Лотт: Вся цель этого - получить более удобный доступ к членам dict или object. Идея состоит в том, что некоторая формула, такая как (a**2+b)*exp(c*d/e), легче читать, чем (n.a**2+n.b)*exp(n.c*n.d/n.e) для некоторых n (возможно, просто обертывание dict, содержащего соответствующие переменные). Что касается плохого дизайна, предположим, что у вас есть некоторые параметры, которые определяют форму набора функций. Например, это могут быть параметры материала, описывающие уравнение состояния и другие свойства газа. - person Jed; 30.05.2011
comment
Каждая функция использует подмножество параметров, и вы не хотите писать набор шаблонов для извлечения только той части, которая действительно нужна. Вы не хотите изменять вызывающих, когда функции требуется больше параметров, и вы не хотите изменять внутренние функции, когда другая часть модели требует введения нового параметра. - person Jed; 30.05.2011

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

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

Не забывайте, что все локальные переменные должны существовать во время компиляции; если вы ссылаетесь на имя, которое не привязано к локальному имени во время компиляции, компилятор считает его глобальным. Вы не можете "создавать" локальные объекты после компиляции.

См. это вопрос для одного возможного решения, но это серьезный взлом, и вы действительно не хотите этого делать.

Обратите внимание, что в вашем примере кода есть основная проблема:

@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

"test.dependencies" не относится к "f.dependencies", где f - текущая функция; это ссылка на фактическое глобальное значение «test». Это означает, что если вы используете более одного декоратора:

@memoize
@depends("a", "b", "c", "d", "e", "f")
def test():
    put_into_locals(test.dependencies)

он больше не будет работать, поскольку "test" - это функция, обернутая в memoize, а не зависимая. Python действительно нуждается в способе ссылки на «выполняющуюся в данный момент функцию» (и класс).

person Glenn Maynard    schedule 20.09.2009

Я бы сохранил его в переменной:

refs    = locals()
def set_pets():
    global refs
    animals = ('dog', 'cat', 'fish', 'fox', 'monkey')
    for i in range(len(animals)):
        refs['pet_0%s' % i] = animals[i]

set_pets()
refs['pet_05']='bird'
print(pet_00, pet_02, pet_04, pet_01, pet_03, pet_05 )
>> dog fish monkey cat fox bird

И если вы хотите протестировать свой диктант, прежде чем помещать его в locals ():

def set_pets():
    global refs
    sandbox = {}
    animals = ('dog', 'cat', 'fish', 'fox', 'monkey')
    for i in range(len(animals)):
        sandbox['pet_0%s' % i] = animals[i]
    # Test sandboxed dict here
    refs.update( sandbox )

Python 3.6.1 на MacOS Sierra

person David Pennell    schedule 18.07.2017

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

>>> import inspect
>>> inspect.currentframe().f_locals['foo'] = 'bar'
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'foo', 'inspect']
>>> foo
'bar'
person dcrosta    schedule 20.09.2009
comment
Это точно так же, как locals (); inspect.currentframe().f_locals is locals() верно. - person Glenn Maynard; 20.09.2009
comment
Это не полностью неправильно, но это работает только тогда, когда фрейм является самым верхним, то есть глобальной областью видимости. Он не будет работать в рамках локальных масштабов. - person bendtherules; 12.06.2015