Python: есть ли способ импортировать переменную с помощью timeit.timeit ()?

Предположим, у меня есть функция, которая принимает массив и меняет каждый элемент на 0.

def function(array):
    for i in range(0,len(array)):
        array[i] = 0
return array

Я хочу проверить, сколько времени требуется этой функции для запуска на случайном массиве, который я хочу сгенерировать ВНЕШНИЙ тест timeit. Другими словами, я не хочу включать время, необходимое для создания массива, во время.

Сначала я сохраняю случайный массив в переменной x и делаю:

timeit.timeit("function(x)",setup="from __main__ import function")

Но это дает мне ошибку: NameError: глобальное имя 'x' не определено

Как я могу это сделать?


person Community    schedule 18.09.2013    source источник
comment
Возможный дубликат Получение глобального имени 'foo' не определено с Python timeit   -  person sds    schedule 20.09.2017
comment
Для Python 3.5+ ознакомьтесь с ответом здесь   -  person Darkonaut    schedule 10.02.2019


Ответы (5)


Импортируйте x из __main__ также:

timeit.timeit("function(x)", setup="from __main__ import function, x")

Как и function, x - это имя в модуле __main__, которое может быть импортировано в настройку timeit.

person Martijn Pieters    schedule 18.09.2013
comment
вау, с моей стороны было глупо не думать об этом. Спасибо, Мартейн! - person ; 19.09.2013
comment
Как мне включить несколько строк настройки? - person Polydynamical; 09.10.2020
comment
@cbracketdash: обычно выполняется настройка до того, как вы доберетесь до этой точки. Я использую только аргумент setup для настройки пространства имен, поэтому импортирую имена из __main__. - person Martijn Pieters; 09.10.2020

Вы можете полностью избежать этой проблемы, если передадите timeit функцию вместо строки. В этом случае функция выполняется в обычном глобальном и закрывающем окружении. Так:

timeit.timeit(lambda: function(x))

Или, если хотите:

timeit.timeit(partial(function, x))

(Подробнее см. здесь. Обратите внимание, что для этого требуется Python 2.6+, поэтому, если вам нужно 2.3-2.5, вы не можете использовать этот трюк.)


Как сказано в документации: «Обратите внимание, что временные издержки в этом случае немного больше из-за дополнительных вызовов функций».

Это означает, что он заставляет сам timeit работать медленнее. Например:

>>> def f(): pass
>>> timeit.timeit('timeit.timeit("f()", setup="from __main__ import f")', setup='import timeit', number=1000)
91.66315175301861
>>> timeit.timeit(lambda: timeit.timeit(f), number=100)
94.89793294097762

Однако на фактические результаты это не влияет:

>>> timeit.timeit(f, number=100000000)
8.81197881908156
>>> timeit.timeit('f()', setup='from __main__ import f', number=100000000)
8.893913001054898

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

Обратите внимание, что фактическое время, затрачиваемое внутри функции, здесь составляет около 88 секунд, поэтому мы почти удвоили накладные расходы на код синхронизации ... но все равно добавили только 3% к общему времени тестирования. И чем менее тривиален f, тем меньше будет эта разница.

person abarnert    schedule 18.09.2013
comment
Но учтите, что вам нужно делать это последовательно во всех тестах, чтобы дополнительные затраты на операции с кадрами стека не влияли на ваши сравнения. - person Martijn Pieters; 19.09.2013
comment
@MartijnPieters: В обычных случаях внутри синхронизируемой части нет дополнительных операций кадра стека, только за счет самой стоимости синхронизации. Например, с пустой функцией я получаю 29,3 нс на цикл в любом случае ... но при запуске timeit на самом timeit требуется 94 мс вместо 91 мс, чтобы найти это 29,3 нс. - person abarnert; 19.09.2013
comment
(Возможно, вы захотите перепроверить мой тест: timeit.timeit(lambda: timeit.timeit(f), number=1000) тривиально; timeit.timeit('timeit.timeit("f()", setup="from __main__ import f")', setup='import timeit', number=1000) немного меньше…) - person abarnert; 19.09.2013
comment
Можно ли передать timeit.timeit() функцию вместо строки, задокументированной где-то, и если да, то укажите мне на нее? Спасибо. - person martineau; 19.09.2013
comment
@martineau: Да, это точно задокументировано там, где я указал в своем ответе. (Версия 3.x здесь, но в основном то же.) - person abarnert; 19.09.2013
comment
В своем ответе вы связались с документацией для класса timeit.Timer, а не для timeit.timeit функция. Тем не менее, ни один из них не выглядит так, будто принимает аргумент, который может быть функцией, которая должна быть синхронизирована - или я что-то упускаю? - person martineau; 19.09.2013
comment
@martineau: Изменено в версии 2.6: параметры stmt и setup теперь также могут принимать объекты, вызываемые без аргументов. Функции являются вызываемыми объектами. timeit.timeit() задокументирована как сокращенная функция для Timer() экземпляров. - person Martijn Pieters; 19.09.2013
comment
@Martijn: А, это, должно быть, уловка, на которую намекал abarnert - думаю, я должен попытаться уделить немного больше внимания измененным в версии - тем, которые я использую примечания. В любом случае, спасибо! - person martineau; 19.09.2013
comment
@martineau: функция timeit говорит «Создать экземпляр таймера с заданным… и запустить его метод timeit, который говорит вам, что нужно посмотреть на Timer, чтобы узнать, что означают аргументы», поэтому я связался с Timer вместо timeit. (Также обратите внимание, что только в документах 2.6 и 2.7 указано "Изменено в версии 2.6; в текущая документация, это просто описано как часть функциональности.) - person abarnert; 19.09.2013
comment
В вашем не влияет на абзац фактического результата, вы на самом деле не показываете, что функциональный подход не добавляет накладных расходов, поскольку вы синхронизируете функцию, которую создали, исключительно для того, чтобы обернуть то, что вы действительно хочу время. Вместо этого сравните: timeit.timeit(lambda: None) и timeit.timeit("pass"). - person detly; 01.07.2014

Импортировать x из __main__:

timeit.timeit("function(x)",setup="from __main__ import function, x")
person Andrew Clark    schedule 18.09.2013

В качестве альтернативы вы можете добавить x к globals. Хорошо то, что он работает в pdb сеансе отладки:

globals()['x'] = x
timeit.timeit(lambda: function(x))

Обратите внимание, что временные издержки в этом случае немного больше из-за дополнительных вызовов функций. [источник]

person Dennis Golomazov    schedule 09.02.2017

В Python 3.5 был введен необязательный аргумент globals. Это позволяет указать пространство имен, в котором должен выполняться оператор timeit.

Итак, вместо того, чтобы писать:

timeit.timeit("function(x), setup="from __main__ import function, x")

... теперь вы можете написать:

timeit.timeit("function(x)", globals=globals())

person Darkonaut    schedule 01.01.2018
comment
Как мне включить несколько строк настройки? - person Polydynamical; 09.10.2020
comment
@cbracketdash Это зависит от того, что вам нужно. Если вашей функции нужен только доступ к глобальным объектам, вы можете выполнить настройку в обычном коде и использовать globals, как показано. Кроме того, или если вы не можете просто использовать глобальные переменные, вы все равно можете использовать параметр setup, например setup="print('line1'); print('line2')", где вы используете точку с запятой для разделения строк. - person Darkonaut; 09.10.2020