Проблема Python с объявлением функции внутри exec

Почему я не могу объявить функцию в exec внутри другой функции? Я действительно понятия не имею, почему это происходит, есть ли способ заставить это работать. Я нашел это: Вызов проблемы с функцией, определенной в exec(), но это не совсем то, что я ищу, в моем случае я не хочу, чтобы код exec был произвольным.

def test():
    exec("""
def this():
    print('this')

def main():
    this()

main()
""")

test()

Возвращает:

Traceback (most recent call last):
  File "/t.py", line 12, in <module>
    test()
  File "/t.py", line 2, in test
    exec("""
  File "<string>", line 8, in <module>
  File "<string>", line 6, in main
NameError: name 'this' is not defined

Когда я выполняю exec вне функции, все работает нормально, почему? :

exec("""
def this():
    print('this')

def main():
    this()

main()
""")

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

Community 21.03.2022 16:34

Не знаю почему, но это работает, если вы добавите пустой словарь как globals к exec. Чего именно вы хотите этим добиться, или чисто из академического интереса?

tobias_k 21.03.2022 16:37
this и main определены в локальной области test, но main по-прежнему ищет в области Глобальный определение this. Когда вы передаете dict, это используется как пространство имен, поэтому вызов main будет искать там вместо определения this.
chepner 21.03.2022 16:53

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

chepner 21.03.2022 16:54

Да, спасибо, я не знал о пространстве имен dict в exec. И то чисто из любопытства.

vcbbcvbcv 22.03.2022 10:51
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
5
47
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

def test():
    exec("""
global this
def this():
    print('this')

def main():
    this()

main()
""")

test()

полученные результаты:

this

однако это может перезаписать другие глобальные переменные, потому что теперь я могу вызывать this() вне test():

>>> this()
this
Ответ принят как подходящий

TLDR: предоставьте явные глобальные словари для выполнения кода «как если бы» он выполнялся на верхнем уровне.


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

def foo():
    bar = 12
    def qux():
        print(bar)  # nested scope accesses outer scope
    return qux

Примечательно, что такой поиск имени происходит во время компиляции исходного кода — компилятор знает, что и foo, и qux имеют доступ к bar, поэтому он должен быть доступен для обоих. Внутренняя функция напрямую компилируется для просмотра переменной внешней функции.

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

def qux():
    print(bar)  # top-level scope accesses global scope

Это проблема, когда мы exec кодируем в функции: определение, такое как def main(): this(), компилируется для поиска this в глобальной области видимости, ноexec выполняется в текущей локальной области видимости функции.

In all cases, if the optional parts are omitted, the code is executed in the current scope.

Таким образом, this является посмотрел вверх глобально, но определенный локально.1 Компилятор анализирует def test и исполняемый код отдельно, поэтому он не знает, что поиски должны рассматриваться как вложенные.
В результате поиск не работает.

Чтобы exec закодировать «как если бы» он выполнялся на верхнем уровне, достаточно предоставить явное globals — например, свежее пространство имен {} или фактическое globals().

def test():
    exec("""
def this():
    print('this')

def main():
    this()

main()
""", {})
#    ^^ globals namespace for exec

test()

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


1Это можно проверить, напечатав globals()/locals() и просмотрев инструкции функции. dis.dis(main) в exec будет производить:

  6           0 LOAD_GLOBAL              0 (this)
              2 CALL_FUNCTION            0
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

Другие вопросы по теме