Библиотека импорта Python3 с exec дает неопределенную переменную ошибку [duplicate]

Полный список возможных полей в форме создания электронной почты, основанной на html:

  • subject
  • cc
  • bcc
  • body





https://codepen.io/garfunkel61/pen/oYGNGp

8
задан Community 23 May 2017 в 12:08
поделиться

2 ответа

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

Давайте начнем с более простой версии вашего кода:

def foo():
    exec("K = 89")
    print(K)

Если вы запустите foo(), вы получите то же самое исключение, которое вы видите с более сложными function:

>>> foo()
Traceback (most recent call last):
  File "<pyshell#167>", line 1, in <module>
    foo()
  File "<pyshell#166>", line 3, in foo
    print(K)
NameError: name 'K' is not defined

Давайте разобраем его и посмотрим, почему:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_GLOBAL              0 (exec)
              3 LOAD_CONST               1 ('K = 89')
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP

  3          10 LOAD_GLOBAL              1 (print)
             13 LOAD_GLOBAL              2 (K)
             16 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             19 POP_TOP
             20 LOAD_CONST               0 (None)
             23 RETURN_VALUE

Операция, на которую вы должны обратить внимание, - это символ «13». Здесь компилятор обрабатывает поиск K в последней строке функции (print(K)). Он использует код операции LOAD_GLOBAL, который терпит неудачу, потому что «K» не является глобальным именем переменной, а является значением в нашем locals() dict (добавлено вызовом exec).

Что если мы уговорили компилятор видеть K как локальную переменную (давая ей значение перед запуском exec), поэтому он будет знать, что не искать глобальную переменную, которая не существует?

def bar():
    K = None
    exec("K = 89")
    print(K)

Эта функция не даст вам ошибки, если вы ее запустите, но вы не получите ожидаемое значение:

>>> bar()
None

Давайте разобрать, чтобы понять, почему:

>>> dis.dis(bar)
  2           0 LOAD_CONST               0 (None)
              3 STORE_FAST               0 (K)

  3           6 LOAD_GLOBAL              0 (exec)
              9 LOAD_CONST               1 ('K = 89')
             12 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             15 POP_TOP

  4          16 LOAD_GLOBAL              1 (print)
             19 LOAD_FAST                0 (K)
             22 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             25 POP_TOP
             26 LOAD_CONST               0 (None)
             29 RETURN_VALUE

Обратите внимание на коды операций, используемые в «3» и «19». Компилятор Python использует STORE_FAST и LOAD_FAST, чтобы поместить значение для локальной переменной K в слот 0 и позже, чтобы вернуть его обратно. Использование пронумерованных слотов значительно быстрее, чем вставка и выборка значений из словаря, подобного locals(), поэтому компилятор Python делает это для всех локальных переменных доступа в функции. Вы не можете перезаписать локальную переменную в слоте, изменив словарь, возвращенный locals() (как это делает exec, если вы не передадите ему dict для использования в своем пространстве имен).

В самом деле, давайте попробуем третий вариант нашей функции, где мы снова заглянем в locals, когда у нас K определяется как регулярная локальная переменная:

def baz():
    K = None
    exec("K = 89")
    print(locals())

Вы не увидите 89 на выходе это тоже!

>>> baz()
{"K": None}

Причина, по которой вы видите старое значение K в locals(), объясняется в документации функции :

< blockquote>

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

Слот, в котором хранится значение локальной переменной K, не было изменено exec, который только модифицирует locals() dict. Когда вы снова вызываете locals(), Python «обновляет [s]» словарь со значением из слота, заменяя значение, хранящееся там на exec.

Вот почему документы продолжаются, чтобы сказать :

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

Ваш вызов exec изменяет dict locals(), и вы обнаруживаете, как его изменения не являются всегда видно вашим более поздним кодом.

14
ответ дан Blckknght 15 August 2018 в 20:58
поделиться
  • 1
    изменения в locals () не гарантируются и на самом деле происходят в других Pythons (я думаю, что IronPython и Jython делают это, а Python 2 - при определенных обстоятельствах). – Ethan Furman 8 August 2014 в 08:54
  • 2
    Да, я должен указать, что все вышеперечисленное разборки происходит от Python 3.4.0. Если вы используете другую версию Python или интерпретатор, отличный от cpython, вы можете получить несколько разные результаты. Мой ответ - это в основном исследование why cpython 3.4 ведет себя так, как он делает. Вы действительно не должны полагаться на специфику вашего кода. – Blckknght 9 August 2014 в 03:35
  • 3
    Спасибо за разборку! – OBu 10 August 2014 в 22:19

В вопросе exec / eval / locals

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

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

«Лучшим» способом использования exec() в этом случае является передача в ваш собственный словарь, а затем работа с значениями в этом .

def foo():
    exec_scope = {}
    exec("y = 2", exec_scope)
    print(exec_scope['y'])
foo()

В этом случае exec_scope используется как глобальная и локальная область для exec, а после exec она будет содержать {'y': 2, '__builtins__': __builtins__} (встроенные встроены для вы, если не присутствуете)

Если вы хотите получить доступ к более глобальным значениям, вы можете сделать exec_scope = dict(globals()).

Передача в разных словарях глобального и локального масштаба может привести к «интересному» поведению.

Если вы передаете один и тот же словарь в последовательные вызовы exec или eval, то они имеют одинаковую область действия, поэтому ваш eval работал (он неявно использовал словарь locals()).

В именах динамических переменных

Если вы задаете имя из строки, что не так, чтобы получить значение как строку (то есть, что делает словарь)? Другими словами, почему вы хотите установить locals()['K'], а затем получить доступ к K? Если K находится в вашем источнике, это не действительно динамически заданное имя ... следовательно, словари.

5
ответ дан Jason S 15 August 2018 в 20:58
поделиться
  • 1
    изменения в locals() не гарантированы и действительно происходят в других Pythons (я думаю, что IronPython и Java Python делают это). – Ethan Furman 8 August 2014 в 02:31
  • 2
    В документах IronPython и Jython говорится, что вы не должны модифицировать locals () и что это может быть & quot; не влияют на состояние интерпретатора. Я беру & quot; не может & quot; чтобы означать ", кроме, может быть, в странных краевых случаях" но замечание о возможности различий между реализациями является хорошим моментом. – Jason S 8 August 2014 в 07:25
  • 3
    Я не использую ни один из них, поэтому не могу сказать, как часто это работает. Моя точка зрения заключалась в том, что эффект модификации locals() не является указанным языком, поэтому «не может» & quot; формулировка. – Ethan Furman 8 August 2014 в 08:56
  • 4
    Спасибо за хороший ответ! Мне было трудно решить, кто должен получить +50 ... – OBu 10 August 2014 в 22:20
Другие вопросы по тегам:

Похожие вопросы: