Почему пространство имён для eval() так работает?

Функция grid выдаёт ошибку name 'row' is not defined, так как функция eval вызывается внутри вызова grid. Однако, в функции place такой проблемы нет, хотя команды те же. Почему eval внутри вызова функции меняет свое пространство имён?

import tkinter as tk

def _parametrize(widget, method, kvs):
  s = "widget.%s(" % method
  r = []
  for u, v in kvs:
    if v != None:
      r += ["%s = %s" % (u, v)]
  s += ", ".join(r) + ")"
  exec(s)

def place(widget, x = None, y = None, width = None, height = None):
  kvs = []
  for s in ("x", "y", "width", "height"):
    kvs += [(s, eval(s))]
  _parametrize(widget, "place", kvs)

def grid(widget, row = None, column = None, rowspan = None, columnspan = None):
  _parametrize(widget, "grid", [(s, eval(s)) for s in ("row", "column", "rowspan", "columnspan")])

r = tk.Tk()

b = Button(r, text = "Button")
place(b, 20, 20, 100, 50)
grid(b)

r.mainloop()

Ответы (1 шт):

Автор решения: Xander

В качестве области видимости eval использует либо глобальную область видимости (если запущен вне функции), либо локальную область видимости (если запущен внутри функции).

У вас как раз случай с локальной областью видимости.

Однако, следует учитывать, что list comprehension создаёт новую локальную область видимости.

В этом легко убедиться:

def foo():

    a = 1

    print(locals())
    # Выведет: {'a': 1}

    [print(locals()) for x in range(1)]
    # Выведет: {'x': 0, '.0': <range_iterator object at 0x7fb02c9288a0>}

foo()

Как видите, в первом случае в локальной области видимости есть переменная a. А во втором - её нет, но есть переменная x.

Чтобы ваш код работал, ему можно явно передать области видимости:

def grid(widget, row = None, column = None, rowspan = None, columnspan = None):
    globs = globals()
    locs = locals()
    _parametrize(widget, "grid", [(s, eval(s, globs, locs))
                                  for s in ("row", "column", "rowspan", "columnspan")])

Но, как уже правильно сказал insolor, не надо использовать exec и eval без очень серьёзной на то причины. А в вашем случае легко можно обойтись без них.

→ Ссылка