Сброс глобальных переменных в timeit.repeat - PullRequest
0 голосов
/ 27 августа 2018

Сценарий

Пусть test будет модулем, который мы запускаем как __main__. Этот модуль содержит одну глобальную переменную с именем primes, которая инициализируется в модуле следующим назначением.

primes = []

Модуль также содержит функцию с именем pi, которая изменяет эту глобальную переменную:

def pi(n):
    global primes
    """Some code that modifies the global 'primes' variable"""

Затем я хочу синхронизировать указанную функцию с помощью встроенного модуля timeit. Я хочу использовать функцию timeit.repeat и получить минимальное значение времени как способ повышения точности измерения (вместо измерения только один раз, которое может быть замедлено из-за несвязанных процессов).

print(min(timeit.repeat('test.pi(50000)',
                        setup="import test",
                        number=1, repeat=10)) * 1000)

Проблема в том, что функция pi ведет себя по-разному в зависимости от значения primes: я ожидал, что для каждого повторения оператор import test в параметре setup будет перезапускать primes = [] оператор в test, таким образом «сбрасывая» primes, чтобы выполняемый код был идентичен для каждого повторения. Но вместо этого используется значение primes, полученное в результате предыдущего выполнения, поэтому мне пришлось добавить оператор test.primes = [] в параметр setup:

print(min(timeit.repeat('test.pi(50000)',
                        setup="import test \n" + "test.primes = []",
                        number=1, repeat=10)) * 1000)


Вопрос

Это подводит меня к вопросу: существует ли прямой способ (т. Е. В одном выражении) «сбросить» значения всех глобальных переменных на те, которые были, когда они были впервые назначены в модуле

В этом конкретном сценарии добавление одного оператора для «сброса» вручную primes работает нормально, но рассмотрим случай, в котором есть много глобальных переменных, и вы хотите «сбросить» их все.


Боковой квест-ион

Почему оператор import test не выполняет повторное начальное primes = [] присваивание?

1 Ответ

0 голосов
/ 28 августа 2018

Давайте начнем с вашего дополнительного вопроса, потому что оказывается, что он на самом деле является центральным для всего:

Почему оператор import test не выполняет повторное начальное primes = [] присваивание? "

Поскольку, как объяснено в документации по системе импорта и оператора import , то, что import test делает, это, в общем, псевдокод:

if 'test' not in sys.modules:
    find, load (compiling if needed), and exec the module
    sys.modules['test'] = result
test = sys['test.modules']

ОК, но почему делает это?

  • Если у вас есть два модуля, которые импортируют один и тот же модуль, они ожидают увидеть одни и те же глобальные переменные. И помните, что типы, функции и т. Д., Определенные на верхнем уровне функции, являются глобальными. Например, если sortedlist.py импортирует collections.abc в class SortedList(collections.abc.Sequence):, а scraper.py импортирует collections.abc в isinstance(something, collections.abc.Sequence), вы бы хотели, чтобы SortedList прошел этот тест, но это не произойдет, если два совершенно независимых типа, потому что они произошли от двух разных модульных объектов, которые имеют одно и то же имя,

  • Если у вас есть 12 модулей, все из которых import pandas as pd, вы будете запускать весь код инициализации Pandas 12 раз. За исключением того, что некоторые из ваших модулей также, вероятно, импортируют друг друга, поэтому каждый из них будет запускаться несколько раз и каждый раз импортировать Pandas. Как вы думаете, сколько времени потребуется, чтобы выполнить все инициализации Панд 60 раз?


Итак, повторное использование существующих модулей - это почти всегда то, что вы хотите.

А если нет, это, как правило, признак того, что с вашим дизайном что-то не так (что вполне может иметь место).

Но «почти всегда» не «всегда». Так что есть способы обойти это. Ни один из них, как правило, не является хорошей идеей для живого кода, но для таких вещей, как модульные тесты и бенчмаркинг, есть три базовых варианта, которые подходят для всех, если вам нужны компромиссы:

  • del sys.modules['test']. Это, очевидно, довольно забавно, но на самом деле это именно то, что вы хотите здесь. Любые существующие ссылки на старый модуль полностью не затронуты, но в следующий раз, когда кто-нибудь сделает import test, он получит совершенно новый модуль test.
  • importlib.reload(test). Это звучит замечательно, но с одной стороны это может быть излишним (обратите внимание, что это вызывает перекомпиляцию исходного кода модуля, что вам не нужно), в то время как с другой стороны этого может быть недостаточно (на самом деле это не сбрасывает глобальные переменные - если ваш код выполняет primes = [] на верхнем уровне, эта строка выполняется, так что кого это волнует, но если вместо этого ваш код выполняет, скажем, globals().setdefault('primes', []) внутри функции pi, вам не все равно).
  • Вместо import test, вручную выполните все шаги по выполнению модуля (см. examples в importlib документах), но не храните его в sys.modules['test'] или в test, просто сохраните его в локальной переменной, которую вы отбрасываете после каждого теста. Это, наверное, самый чистый, хотя он означает 6 строк кода вместо 1.
...