Почему я не могу объявить функцию в 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()
""")
Не знаю почему, но это работает, если вы добавите пустой словарь как globals
к exec
. Чего именно вы хотите этим добиться, или чисто из академического интереса?
this
и main
определены в локальной области test
, но main
по-прежнему ищет в области Глобальный определение this
. Когда вы передаете dict
, это используется как пространство имен, поэтому вызов main
будет искать там вместо определения this
.
Прежде чем беспокоиться об этом, убедитесь, что вы необходимостьexec
. То, что (вы думаете) вы используете могуexec
, не означает, что вы используете его должен.
Да, спасибо, я не знал о пространстве имен dict в exec. И то чисто из любопытства.
вот моя попытка заставить это работать
из ошибки я предполагаю, что вы можете создать объект, используя 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
Пожалуйста, уточните вашу конкретную проблему или предоставьте дополнительную информацию, чтобы выделить именно то, что вам нужно. Как сейчас написано, трудно точно сказать, о чем вы спрашиваете.