Почему генератор Python путает свою область действия с глобальной в exec'd-скрипте?

Итак, я работаю в среде, где сценарий конфигурации для инструмента является execСценарий Вызов exec выглядит примерно так:

outer.py:

exec(open("inner.py").read(), globals(), {})

Теперь я хочу сделать некоторые относительно основные итерации в execСценарий В этом случае выполняем работу, когда некоторые значения отсутствуют в белом списке:

inner.py:

items = (
  'foo/bar',
  'foo/baz',
  'foof',
  'barf/fizz',
)

whitelist = (
  'foo/',
)

for key in items:
  try:
    # Not terribly efficient, but who cares; computers are fast.
    next(True for prefix in whitelist if key.startswith(prefix))

  # Do some work here when the item doesn't match the whitelist.
  except StopIteration:
    print("%10s isn't in the whitelist!" % key)

Бег python inner.py дает ожидаемый результат:

      foof isn't in the whitelist!
 barf/fizz isn't in the whitelist!

Вот странная часть: Бег python outer.py кажется, показывает, что интерпретатор смущен областью действия генератора:

Traceback (most recent call last):
  File "outer.py", line 1, in <module>
    exec(open("inner.py").read(), globals(), {})
  File "<string>", line 15, in <module>
  File "<string>", line 15, in <genexpr>
NameError: global name 'key' is not defined

Некоторые другие заметки:

  • Вы можете print(key) просто отлично внутри for цикл (до запуска генератора).

  • Замена пустого слова locals() в exec линия outer.py решает проблему, но этот код находится вне моего контроля.

  • Я использую OS X, собранный Python 2.7.2 (Mountain Lion).

2 ответа

Решение

Действительно хитроумно.

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

Если два отдельных объекта задаются как глобальные и локальные, код будет выполняться так, как если бы он был встроен в определение класса.

(Обратите внимание, что при запуске exec(..., globals(), locals()) на уровне модуля это не применяется, потому что globals() is locals()т.е. не два отдельных объекта).

Это означает, что вы можете просто воспроизвести проблему, запустив этот скрипт:

class A(object):

  items = (
    'foo/bar',
    'foo/baz',
    'foof',
    'barf/fizz',
  )

  whitelist = (
    'foo/',
  )

  for key in items:
    try:
      # Not terribly efficient, but who cares; computers are fast.
      next(True for prefix in whitelist if key.startswith(prefix))
      # Found!
      print(key)
    except StopIteration:
      pass

"Хорошо, но почему я получаю ошибку здесь?"

Так рада, что ты спросил. Ответ здесь.

Почему бы вам просто не добавить внутренний цикл for:

for key in items:
    for prefix in whitelist:
        if key.startswith(prefix):
            print(key)

Я уверен, что вы не получите эту ошибку, и она намного проще / легче для чтения.

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