Почему eval не может найти переменную, определенную во внешней функции?

Я знаю, что использование eval() обычно означает "плохой код", но я наткнулся на странное поведение функции eval() во внутренних функциях, которые я не мог понять. Если мы напишем:

def f(a):
    def g():
        print(eval('a'))
    return g()

Запуск f(1) в этом случае дает NameError, утверждая, что a не определен. Однако, если мы определим

def f(a):
    def g():
        b = a + 1
        print(eval('a'))
    return g()

Затем выполнение f(1) печатает 1.

Что-то происходит с локальными и глобальными переменными, которые я не могу понять. Является a только локальная переменная в g() когда она "используется" для чего-то? Что здесь происходит?

Ответ 1

Короче говоря, поскольку eval предназначен для динамической оценки, интерпретатор не знает, что он должен добавить a в локальную область g. Для эффективности интерпретатор не будет добавлять ненужные переменные в dict локальных переменных.

Из документа для eval:

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

Это означает, что функции eval(expression) будут использовать globals() как глобальную область по умолчанию и locals() как свою локальную область, если ни одна из них не предоставляется.

Хотя, в вашем первом примере нет a в одном.

def f(a):
    print("f locals:", locals())
    def g():
        print("g locals:", locals())
        print(eval('a'))
    return g()

f(1)

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

Чтобы он работал, вам нужно указать nonlocal a в g.

Выход

f locals: {'a': 1}
g locals: {}
Traceback ...
...
NameError: name 'a' is not defined

В вашем втором примере a находится в g локальных переменных, так как используется в области.

def f(a):
    print("f locals:", locals())
    def g():
        print("g locals:", locals())
        b = a + 1
        print("g locals after b = a + 1:", locals())
        print(eval('a'))
    return g()

f(1)

Выход

f locals: {'a': 1}
g locals: {'a': 1}
g locals after b = a + 1: {'a': 1, 'b': 2}
1

Ответ 2

Похоже, что eval() может искать переменные только в локальной (здесь g) или глобальной, но не в родительской среде (здесь f). Обходной путь - установить переменную как глобальную.

 
def f(a):    
    global b #note, can not use "global a" directly,  will get error:"name 'a' is parameter and global"
    b=a
    def g():
        print(eval('b'))
    return g()
f(1)

выход: 1