Странное поведение с использованием timeit + exec - PullRequest
2 голосов
/ 12 марта 2020

Я столкнулся с каким-то странным поведением, пытаясь рассчитать время python сценариев. Минимальный пример:

foobar.py:

foo = 'Hello'
print(''.join(c for c in foo if c not in 'World'))
print(''.join(c for c in 'World' if c not in foo))

timer.py:

import timeit
timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)

Когда я запускаю foobar.py, я получаю ожидаемый результат:

> python3 foobar.py 
He
Wrd

Однако, когда я запускаю timer.py, я получаю следующую ошибку:

> python3 timer.py
He
Traceback (most recent call last):
  File "timer.py", line 2, in <module>
    timeit.repeat(stmt="exec(open('foobar.py').read())", repeat=1, number=1)
  File "/usr/lib/python3.7/timeit.py", line 237, in repeat
    return Timer(stmt, setup, timer, globals).repeat(repeat, number)
  File "/usr/lib/python3.7/timeit.py", line 204, in repeat
    t = self.timeit(number)
  File "/usr/lib/python3.7/timeit.py", line 176, in timeit
    timing = self.inner(it, self.timer)
  File "<timeit-src>", line 6, in inner
  File "<string>", line 3, in <module>
  File "<string>", line 3, in <genexpr>
NameError: name 'foo' is not defined

Возможно, самое странное в этом то, что первый оператор печати в foobar.py работает нормально а второй нет. Выполнение foobar.py с использованием exe c без оболочки timeit также работает нормально.

У кого-нибудь есть объяснение этому странному поведению?

1 Ответ

1 голос
/ 12 марта 2020

Это на самом деле не ограничивается timeit в сочетании с exec, но проблема только с exec: операторы выполняются в локальном пространстве имен, а генератор внутри str.join использует другой (новый) локальный пространство имен, где ранее установленный foo неизвестен.

Блоки определения класса и аргументы для exec() и eval() являются особыми в контексте разрешения имен. Определение класса - это исполняемый оператор, который может использовать и определять имена. Эти ссылки следуют обычным правилам разрешения имен, за исключением того, что несвязанные локальные переменные ищутся в глобальном пространстве имен. Пространство имен определения класса становится словарем атрибутов класса. Область имен, определенных в блоке класса, ограничена блоком класса; он не распространяется на блоки кода методов - это включает в себя понимания и выражения генератора, поскольку они реализованы с использованием области действия функции. Это означает, что следующее не удастся:

class A:
    a = 42
    b = list(a + i for i in range(10))

Источник: https://docs.python.org/3/reference/executionmodel.html#resolution -of-names

Также см. Понимание списка в exe c с пустыми локальными переменными: например, NameError .

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

timeit.repeat(stmt="exec(open('foobar.py').read(), locals())", repeat=1, number=1)

Или вы можете просто сбросить exec и использовать import:

timeit.repeat(stmt="import foobar", repeat=1, number=1)

Кстати, выполнение exec(open('foobar.py').read() напрямую также работает, только если вы на глобальном (модульном) уровне. Если вы просто поместите его в функцию, она перестанет работать и отобразит ту же ошибку, что и при вызове timeit.

...