У нас когда-то был мини-проект: сделать свой таймер-напоминалку, который спрашивает, про что вам напомнить, а потом выдаёт сообщение через нужное время. В прошлый раз мы его сделали на JavaScript, теперь напишем на Python. Потому что Python — это модно, красиво и приятно.
Отличия и особенности
JavaScript прекрасен тем, что его можно запустить в консоли любого современного браузера. Это для него родная среда, и JS легко работает со страницами, объектами на ней, вкладками браузера и всем, что с ним связано.
Python — более универсальный язык, который работает не только с браузерами, поэтому для него нужен отдельный интерпретатор. Интерпретатор — это программа, которая берёт исходный код и выполняет команду за командой. Вы можете написать отличный код, но чтобы его исполнить, вам всегда нужен будет интерпретатор.
Есть два способа запустить Python-код:
1. Поставить Python себе на компьютер — этот способ хорош, если вы решили основательно изучить язык или просто любите, когда всё быстро и под контролем. Скачать Python можно с официального сайта — есть версии для всех основных операционных систем.
Из минусов — нужно разбираться в параметрах установки и настройки и уметь работать с командной строкой. Плюсы — полный контроль и быстродействие.
2. Использовать онлайн-сервисы, например, этот: onlinegdb.com/online_python_compiler. Работает точно так же — пишете код, нажимаете кнопку Run и смотрите на результат.
Минусы: так как это онлайн-сервис, им пользуется одновременно много человек, поэтому быстродействия от него ждать не стоит. С подключением внешних модулей тоже могут возникнуть проблемы, но с этим можно разобраться, если потратить немного времени.
Плюс: не нужно ничего настраивать и устанавливать, всё работает сразу из браузера. Есть подсветка синтаксиса, сообщения об ошибках и возможность сохранения кода.
Сейчас мы напишем таймер с оглядкой на онлайновый сервис. А отдельно ещё расскажем об установке.
Исходный код на JavaScript
// Спрашиваем текст напоминания, который нужно потом показать пользователю
var text = prompt('О чём вам напомнить?');
// Тут будем хранить время, через которое нужно показать напоминание
var time = prompt('Через сколько минут?');
// Чтобы работать со временем в JavaScript, нам нужно перевести его в миллисекунды. Для этого число минут умножаем на 60 тысяч:
time = time * 60 * 1000;
// Ставим таймер на нужное время с помощью функции setTimeout
setTimeout(function () {
// Выводим на экран текст напоминания, который хранится в переменной text
alert(text);
// Привлекаем внимание к окну мигающим заголовком
titleAlert();
// В самом конце функции указываем время, через которое она должна сработать
}, time);
Что мы здесь сделали:
- спросили текст напоминания;
- выяснили, через сколько минут нужно напомнить;
- поставили таймер на нужное время;
- в нём написали, что когда время выйдет, надо вывести сообщение и помигать заголовком страницы.
Особенность Python в том, что в нём нет встроенных средств работы с браузером и его вкладками, поэтому помигать заголовком пока не получится. С другой стороны, Python не зависит от браузера, поэтому будем использовать штатные средства ввода и вывода сообщений.
Простая реализация на Python
Самое простое, что можно сделать — поставить программу на паузу на нужное время, а потом вывести сообщение. Для этого подключаем стандартный модуль time — он отвечает за работу со временем.
Модуль в Python — это уже готовый python-файл, где собраны запчасти, которые помогают решать какую-то узкую задачу: функции и классы. Например, замерять время, работать с математическими функциями или календарём.
Чтобы сделать паузу, используют команду time.sleep(). Time — это название модуля, который мы подключили, а sleep — функция, которая находится внутри модуля. Её задача — подождать нужное количество секунд, а потом продолжить выполнение программы.
# Подключаем нужный модуль
import time
# Спрашиваем текст напоминания, который нужно потом показать пользователю
print("О чём вам напомнить?")
# Ждём ответа пользователя и результат помещаем в строковую переменную text
text = str(input())
# Спрашиваем про время
print("Через сколько минут?")
# Тут будем хранить время, через которое нужно показать напоминание
local_time = float(input())
# Переводим минуты в секунды
local_time = local_time * 60
# Ждём нужное количество секунд, программа в это время ничего не делает
time.sleep(local_time)
# Показываем текст напоминания
print(text)
Что дальше: многозадачность и оптимизация
Наша программа уже работает как нужно, но её можно улучшить. Дело в том, что ставить весь код на паузу — не самое удачное решение с точки зрения производительности. Представьте, что вам нужно поставить себе несколько напоминаний на разное время. С таким подходом нам придётся выяснять, какое сработает раньше, потом корректировать время паузы для следующего напоминания и так далее.
Можно сделать так: выносить напоминания в отдельные потоки. Это как подпрограмма, которая работает параллельно с нашей программой и не сильно зависит от неё. Это позволит не ждать первого события, а запускать их одновременно. Но про всё это — в следующем материале.
Time tracking is critical to managing your projects. If you’re programming in Python, you’re in luck: The language gives you tools to build your own timers. A timer program can be useful for not only monitoring your own time, but measuring how long your Python program takes to run.
In this article, we’ll present two simple Python timers before looking at the modules you’ll need to create a timer program. We’ll then use these modules to build a stopwatch and a countdown timer, and show you how to measure a Python program’s execution time.
Understanding Timers in Python
A timer in Python is a time-tracking program. Python developers can create timers with the help of Python’s time modules. There are two basic types of timers: timers that count up and those that count down.
Stopwatches
Timers that count up from zero are frequently called stopwatches. We can use these to record the amount of time it takes to complete a task.
Runners often use stopwatch timers to record how long it takes them to complete a lap around a course or finish an entire run. You can use a stopwatch to track how long it takes to complete any task, such as coding a simple program.
Programmers often use stopwatch timers to compare the performance of various Python solutions. By seeing how long each solution takes to execute, you can choose the program that runs the fastest. Over time, this will allow you to better understand computational complexity and build intuition for choosing efficient solutions. These skills go a long way towards becoming a proficient programmer.
Countdown Timers
There are also countdown timers, set with a specific amount of time that depletes until the timer reaches zero. You can use Python to build countdown timers that serve different purposes.
For instance, you can build a cooking countdown timer to ensure you don’t leave food in the oven for too long. Countdown timers can also work to display the time remaining in a sporting event or during an exam. They are also used to count down the hours and minutes to a movie release or big event. The possibilities are endless!
Modules in Python
To create a simple timer in Python, you’ll need to call upon Python’s time
and datetime
modules.
Modules in Python are files that contain classes, functions, variables, and runnable code. By importing a module into your program, you can make use of each component inside the module.
The Python programming language has over 200 predefined modules within its standard library. You can even create your own modules to use in future programs.
The Python Time Module
One of the 200 modules in Python’s standard library is the time module. This module contains the functions we’ll need to build a simple timer in Python.
To use the time
module in Python, we first import it into our program:
Listing modules at the beginning of a Python program makes the functions within the module available for use in our program. The time
module holds more functions than we’ll cover here, but here are the most important ones:
The time.time() function
We call the time()
function, located in the time module, as time.time()
. The first time
references the module, whereas the second is the function itself. The time()
function returns the number of seconds that have passed since the epoch.
In the computing context, we refer to an “epoch” as the time according to which a computer calculates its timestamp values. Windows and most UNIX devices set the epoch as January 1, 1970 at 00:00:00.
When we use time.time()
, we can see the amount of time that has passed since the epoch. We can even use time.ctime()
to convert this number of seconds to the current local time. Take a look at this example:
import time seconds = time.time() print("Time in seconds since the epoch:", seconds) local_time = time.ctime(seconds) print("Local time:", local_time)
In this example, we use time.time()
to see how many seconds have elapsed since the epoch. We then convert this length of time to our current local time with time.ctime()
. This program outputs the following information:
Time in seconds since the epoch: 1630593076.1314547 Local time: Thu Sep 2 10:31:16 2021
It has been over 1.5 billion seconds since the epoch.
The time.sleep() Function
The other function that we’ll need to build our timer is time.sleep()
. This function creates a delay in a program that we can use to count time. time.sleep()
takes a float argument representing the number of seconds that the program will pause for. Plug the example below into your Python integrated development environment (IDE) to see how time.delay()
works:
import time print("This is the start of the program.") time.sleep(5) print("This prints five seconds later.")
Since we set time.sleep()
to five seconds, the program idles for that length of time between outputs.
The Python Datetime Module
datetime
is intended for manipulating dates. We’ll use this module to convert different timestamps into a common format that allows us to compare them.
The datetime.timedelta() Function
We can use timedelta()
to express the difference between two dates and/or two times. In the example below, we use timedelta()
to show the date one year from now:
import datetime current = datetime.datetime.now() print ("Current date:", str(current)) one_year = current + datetime.timedelta(days = 365) print("The date in one year:", str(one_year))
timedelta()
adds 365 days to the current date and outputs the date and time one year from now, down to the microsecond:
Current date: 2021-09-02 14:44:19.429447 The date in one year: 2022-09-02 14:44:19.429447
A Simple Lap Timer (Stopwatch)
Let’s use what we’ve learned to build a stopwatch. Our stopwatch is a lap timer that displays the time to complete a lap as well as total time. Check out the code below:
import time # Timer starts starttime = time.time() lasttime = starttime lapnum = 1 value = "" print("Press ENTER for each lap.nType Q and press ENTER to stop.") while value.lower() != "q": # Input for the ENTER key press value = input() # The current lap-time laptime = round((time.time() - lasttime), 2) # Total time elapsed since the timer started totaltime = round((time.time() - starttime), 2) # Printing the lap number, lap-time, and total time print("Lap No. "+str(lapnum)) print("Total Time: "+str(totaltime)) print("Lap Time: "+str(laptime)) print("*"*20) # Updating the previous total time and lap number lasttime = time.time() lapnum += 1 print("Exercise complete!")
After importing the time
module, we set time.time()
to the start time of our timer. The stopwatch will count forward from this time. We use the variable lasttime
to catalog each lap, whereas totaltime
holds the value for the entire time the stopwatch runs. lapnum
counts the number of laps we run. Finally, we set the value
variable so the user can type “Q” or “q” to end the program.
Time increments in the background while we run the program. Each time the user presses the Enter key, the program displays their lap number, lap time, and total time. Even if you don’t need to record laps, you can still use this timer to track the total time you’re performing a task.
A Simple Countdown Timer
Let’s now build a countdown timer. This example incorporates both the datetime module and the time.sleep()
function. Take a look at the program below:
import time import datetime # Create class that acts as a countdown def countdown(h, m, s): # Calculate the total number of seconds total_seconds = h * 3600 + m * 60 + s # While loop that checks if total_seconds reaches zero # If not zero, decrement total time by one second while total_seconds > 0: # Timer represents time left on countdown timer = datetime.timedelta(seconds = total_seconds) # Prints the time left on the timer print(timer, end="r") # Delays the program one second time.sleep(1) # Reduces total time by one second total_seconds -= 1 print("Bzzzt! The countdown is at zero seconds!") # Inputs for hours, minutes, seconds on timer h = input("Enter the time in hours: ") m = input("Enter the time in minutes: ") s = input("Enter the time in seconds: ") countdown(int(h), int(m), int(s))
Our countdown timer works by having a user input the number of hours, minutes, and seconds for the timer. You can find the code for these inputs at the bottom of the program below the countdown
class.
The countdown
class itself takes those inputs and converts them into a total number of seconds. As long as total_seconds
is greater than zero, the while loop runs. Within the loop, we use the timedelta()
function to calculate the time left on the timer.
The program prints the time left in hours:minutes:seconds format for the user to see. Immediately after, the program pauses for one second, reduces total_seconds
by one second, and repeats the while loop. The loop continues until total_seconds
reaches zero, at which point the program leaves the while loop and prints “Bzzzt! The countdown is at zero seconds!”
Measure Program Execution Time
You can use Python to measure the time it takes for a program to finish execution. This is not very different from what we’ve already done before: We define a starting point, run the program whose time we want to measure, and specify the time at which the program finishes execution. We get the program’s execution time by subtracting the start time point from the end time point.
Let’s compare a few solutions for finding an element within a list. We’ll create a list of 10,000,000 elements and see how long it takes each solution to find the number 700,000.
First, we’ll build a program that makes random guesses until it finds the answer.
vimport time import random our_list = list(range(10000000)) element = 7000000 start = time.time() random_choice = random.choice(our_list) while random_choice != element: random_choice = random.choice(our_list) end = time.time() print(end - start)
The program took 16 seconds to find the element; that’s clearly no good.
Let’s try a better approach. This time, instead of making random guesses, the program will make one pass through the list to look for the element.
import time our_list = list(range(10000000)) element = 7000000 start = time.time() for el in our_list: if el == element: break end = time.time() print(end - start)
This program took only 0.6 seconds. That’s a lot better.
Let’s see if we can beat this time with a more sophisticated algorithm called “binary search.” We borrow the code for the algorithm from this GitHub page.
import time our_list = list(range(10000000)) element = 700000 start = time.time() binary_search(our_list, element) end = time.time() print(end - start)
With only 0.1 seconds required to find the element, we’ve found the winning solution!
These examples show the importance of time-tracking if you’re looking to build scalable programs. When you build a program that ends up being used by many people, every fraction of a second saved on computation time helps.
Start Your Python Journey With Udacity
In this article, we explored the time
and datetime
modules to understand how time works in Python. We used this knowledge to create a lap timer, a countdown timer, and showed you how to measure the execution time of Python programs.
Want to go beyond building timers and dive into fields like app development, machine learning, and data science?
Take your first step by enrolling in Udacity’s Introduction to Programming Nanodegree.
В этом руководстве рассказывается, как создать таймер обратного отсчета в Python.
Код принимает ввод того, как долго должен быть обратный отсчет, и начинает обратный отсчет сразу после ввода ввода.
Использование модуля time
и функции sleep()
для создания таймера обратного отсчета в Python
Модуль time
— это общий модуль Python, содержащий вспомогательные функции и переменные, связанные со временем. Основная функция, используемая в этом руководстве, — это функция sleep()
, которая представляет собой асинхронную функцию, которая приостанавливает выполнение одного потока на n
секунд.
Если ваша программа однопоточная, как в этом руководстве, то функция sleep()
остановит выполнение всей программы до тех пор, пока не будет достигнуто заданное время. Благодаря этому, наряду с подтвержденным пользовательским вводом, мы можем создать простой таймер обратного отсчета в Python.
Первое, что нужно сделать, это импортировать модуль time
для использования функции sleep()
.
Затем объявите функцию, которая будет работать как таймер обратного отсчета. Назовем эту функцию countdown()
. Функция принимает единственный параметр: количество секунд (num_of_secs
), до которого будет отсчитывать таймер.
Переменная num_of_secs
будет непрерывно уменьшаться в цикле, пока не достигнет 0
(что переводится в False
и завершает цикл без каких-либо дополнительных условий).
Внутри цикла отформатируйте входную переменную num_of_secs
в формат MM:SS
и распечатайте ее каждый раз, когда она уменьшается. Для этого используйте встроенную функцию Python divmod()
, которая принимает два числа и возвращает произведение и остаток от двух чисел соответственно. Затем отформатируйте результат кортежа divmod()
в формат MM:SS
с помощью встроенной строковой функции format()
.
def countdown(num_of_secs):
while num_of_secs:
m, s = divmod(num_of_secs, 60)
min_sec_format = '{:02d}:{:02d}'.format(m, s)
{:02d}
форматирует аргумент в 2-значное целое число (из-за символа 02d
). Если целое число меньше двух цифр, к нему добавляются ведущие 0
.
Затем при каждой итерации цикла вызывайте time.sleep(1)
, что означает, что каждая итерация откладывается на 1 секунду и будет продолжаться по истечении.
Перед вызовом функции sleep()
распечатайте отформатированную строку, напоминающую формат MM:SS
текущего значения входной переменной num_of_secs
.
Кроме того, добавьте еще один аргумент в функцию print()
со свойством end
и значением /r
, который представляет собой новую строку для имитации поведения реального таймера. Этот аргумент перезаписывает предыдущий вывод print()
каждый раз при выполнении цикла, перезаписывая все до того, как возврат каретки обозначен символом /r
.
def countdown(num_of_secs):
while num_of_secs:
m, s = divmod(num_of_secs, 60)
min_sec_format = '{:02d}:{:02d}'.format(m, s)
print(min_sec_format, end='/r')
time.sleep(1)
num_of_secs -= 1
print('Countdown finished.')
После этого уменьшите входную переменную min_sec_format
на 1 после выполнения всех предыдущих строк.
Наконец, распечатайте заключительный оператор вне цикла, который означает, что таймер завершил выполнение. Он завершает метод countdown()
и будет работать как таймер обратного отсчета.
Следующее, что нужно сделать, — предоставить пользователю возможность ввести количество секунд для запуска обратного отсчета. Для этого мы можем использовать встроенную функцию input()
для приема пользовательского ввода.
Перехватите ввод в переменную и используйте его в качестве аргумента для функции обратного отсчета. Обязательно приведите входную переменную к int
для проверки.
inp = input('Input number of seconds to countdown: ')
countdown(int(inp))
Весь код должен выглядеть так:
import time
def countdown(num_of_secs):
while num_of_secs:
m, s = divmod(num_of_secs, 60)
min_sec_format = '{:02d}:{:02d}'.format(m, s)
print(min_sec_format, end='/r')
time.sleep(1)
num_of_secs -= 1
print('Countdown finished.')
inp = input('Input number of seconds to countdown: ')
countdown(inp)
Выход:
Окончательный результат будет отображать Countdown Finished
, но будет имитировать работу таймера и очищать каждую строку print()
, пока не достигнет 00:00
.
Итак, если вы введете 5
секунд, трассировка стека будет выглядеть так:
00:05
00:04
00:03
00:02
00:01
Countdown finished.
Вот и все. Теперь вы успешно создали простой таймер обратного отсчета в Python, используя только встроенные функции и функцию sleep()
из модуля time
.
27 июня 2020 г. | Python
Хотя многие разработчики признают Python эффективным языком программирования, программы
на чистом Python могут работать медленнее, чем их аналоги на скомпилированных языках, таких
как C, Rust и Java. В этом руководстве вы узнаете, как использовать таймеры Python для
отслеживания скорости выполнения ваших программ.
В этом уроке вы узнаете, как использовать:
time.perf_counter()
для измерения времени- Классы для сохранения состояния
- Контекстные менеджеры для работы с блоком кода
- Декораторы для настройки функций
Вы также получите базовые знания о том, как работают классы, контекстные менеджеры и декораторы.
Поскольку будут приведены примеры каждой концепции, вы сможете по желанию использовать одну
или несколько из них в своём коде, как для замера времени выполнения кода, так и для других применений.
Каждый метод содержит свои преимущества, и вы узнаете, какие из них использовать в зависимости от
ситуации. Кроме того, у вас будет рабочий таймер Python, который вы можете использовать для
мониторинга ваших программ!
Таймеры Python
Во-первых, познакомимся с некоторыми примерами кода, которые вы будете использовать
на протяжении всего урока. Позже вы добавите в этот код таймер Python, для мониторинга
его производительности. Вы также увидите некоторые из самых простых способов
измерения времени выполнения примера.
Функции таймера Python
Если вы посмотрите на встроенный модуль time в Python, то заметите несколько функций,
которые могут измерять время:
monotonic()
perf_counter()
process_time()
time()
Python 3.7 ввел несколько новых функций, таких как thread_time()
, а также наносекундные версии всех
вышеперечисленных функций, именуемых с суффиксом _ns
. Например, perf_counter_ns()
— это
наносекундная версия perf_counter()
. Подробности об этих функциях будет расказано позже.
А пока обратите внимание на то, что говорится в документации о perf_counter()
:
Возвращает значение (в долях секунд) счётчика производительности,
то есть часов с самым высоким доступным разрешением для измерения
короткого промежутка времени.
Во-первых, вы будете использовать perf_counter()
для создания Python таймера. Позже вы
сравните это с другими функциями таймера Python и узнаете, почему perf_counter()
обычно является лучшим выбором.
Пример: Последовательность Фибоначчи
Чтобы лучше сравнить различные способы добавления таймера Python к своему коду, вы будете применять
разные функции таймера Python к одному и тому же примеру кода в данном руководстве. Если у вас уже есть
код, который вы хотели бы измерить, смело следуйте этим примерам.
Пример, который вы увидите в этом учебнике — это простая итеративная функция, которая вычисляет число
по номеру в последовательности Фибоначчи.
Числа Фибоначчи – это ряд чисел, в котором каждое следующее число равно сумме двух
предыдущих: 1, 1, 2, 3, 5, 8, 13, … . Иногда ряд начинают с нуля: 0, 1, 1, 2, 3, 5, … . В данном
случае мы будем придерживаться первого варианта.
Вычисление n-го числа ряда Фибоначчи с помощью цикла while:
def fibo(n):
fib1 = 1
fib2 = 1
i = 0
while i < n - 2:
fib_sum = fib1 + fib2
fib1 = fib2
fib2 = fib_sum
i = i + 1
return fib2
Сохраним пример в файле с именем fibo.py
. Код состоит из одной функции, которая вычисляет
и печатает n элемент последовательности.
Ваш первый таймер Python
Давайте добавим в пример простой Python-таймер с помощью time.perf_counter()
. Опять же, это
счётчик производительности, который хорошо подходит для замеров времени выполнения
частей кода.
perf_counter()
измеряет время в секундах с некоторого неопределенного момента времени,
что означает, что возвращаемое значение одного вызова функции бесполезно. Однако,
когда вы посмотрите на разницу между двумя вызовами perf_counter()
, вы сможете выяснить,
сколько секунд прошло между двумя вызовами:
>>> import time
>>> time.perf_counter()
32311.48899951
>>> time.perf_counter() # Несколько секунд спустя
32315.261320793
В этом примере вы сделали два вызова perf_counter()
с интервалом почти 4 секунды. Вы можете
подтвердить это, рассчитав разницу между двумя выходами: 32315.26 — 32311.49 = 3.77.
Теперь добавим таймер Python к коду примера:
# series_number.py
import time
from fibo import fibo
def main():
"""Печать 1000 элемента последовательности Фибоначчи"""
tic = time.perf_counter()
result = fibo(1000)
toc = time.perf_counter()
print(f"Вычисление заняло {toc - tic:0.4f} секунд")
print(result)
if __name__ == "__main__":
main()
Обратите внимание, что perf_counter()
вызывается как до, так и после вычисления значения
функции. Затем печатается время, необходимое для вычисления, вычисляя разницу
между двумя вызовами.
Примечание: в строке
print(f"Вычисление заняло {toc - tic:0.4f} секунд")
буква f перед строкой
указывает на то, что это f-строка, что является удобным способом форматирования текстовой
строки. ::0.4f
— это спецификатор формата, который говорит, что числоtoc - tic
должно быть
напечатано как десятичное число с четырьмя десятичными знаками.f-строки доступны только в Python 3.6 и более поздних версиях. Для получения
дополнительной информации ознакомьтесь с статьей по
форматированию строк в Python.
Теперь, когда вы запустите пример, вы увидите потраченное время на вычисления:
$ python series_number.py
Вычисление заняло 0.0004 секунд
[ ... Полный текст результата вычислений ... ]
Мы рассмотрели основы тайминга Python кода. В оставшейся части руководства вы узнаете,
как можно обернуть Python-таймер в класс, менеджер контекста и декоратор, чтобы сделать его
более консистентным и удобным в использовании.
Python класс Timer
Посмотрите, как вы добавили таймер Python в приведённый выше пример. Обратите внимание,
что вам нужна хотя бы одна переменная (tic) для хранения состояния таймера Python перед
началом вычислений. Теперь создадим класс, который будет выполнять те же действия, что
и ручные вызовы perf_counter()
, но более читабельно и консистентно.
В этом руководстве будет создадан и модифицирован класс Timer, который вы можете использовать
для определения таймингов кода несколькими различными способами.
Понимание классов в Python
Классы являются основными строительными блоками объектно-ориентированного программирования.
Класс — это шаблон, который вы можете использовать для создания объектов. Хотя Python не заставляет
вас программировать объектно-ориентированным способом, классы используются повсюду в языке.
Для быстрого доказательства давайте рассмотрим модуль time
:
>>> import time
>>> type(time)
<class 'module'>
>>> time.__class__
<class 'module'>
type()
возвращает тип объекта. Здесь вы можете видеть, что модули на самом деле являются
объектами, созданными из класса module. Специальный атрибут .__class__
используется для
получения доступа к классу, который определяет объект. На самом деле, почти всё в
Python — это класс:
>>> type(3)
<class 'int'>
>>> type(None)
<class 'NoneType'>
>>> type(print)
<class 'builtin_function_or_method'>
>>> type(type)
<class 'type'>
В Python классы отлично подходят, когда вам нужно смоделировать что-то, что должно
отслеживать определенное состояние. В общем случае класс — это набор свойств (называемых
атрибутами) и поведений (называемых методами).
Создание класса таймера Python
Классы хороши для отслеживания состояния. В классе Timer вы хотите отслеживать, когда
запускается таймер и сколько времени прошло с момента реализации. Для первой
реализации Timer
создадим атрибут ._start_time
, а также методы .start()
и .stop()
.
Добавим следующий код в файл с именем timer.py
:
# timer.py
import time
class TimerError(Exception): #5
"""Пользовательское исключение, используемое для сообщения об ошибках при использовании класса Timer"""
class Timer: #9
def __init__(self):
self._start_time = None
def start(self):
"""Запуск нового таймера"""
if self._start_time is not None:
raise TimerError(f"Таймер уже работает. Используйте .stop() чтобы его остановить")
self._start_time = time.perf_counter()
def stop(self):
"""Отстановить таймер и сообщить о времени вычисления"""
if self._start_time is None:
raise TimerError(f"Таймер не работает. Используйте .start() для его запуска")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
print(f"Вычисление заняло {elapsed_time:0.4f} секунд")
Здесь происходит несколько разных вещей, поэтому давайте пройдемся по коду шаг
за шагом.
В строке с комментарием #5 определяется класс TimerError
. Обозначение (Exception
) означает, что TimerError
наследует от другого класса с именемException
. Python использует этот встроенный класс
для обработки ошибок. Вам не нужно добавлять какие-либо атрибуты или методы в TimerError
.
Тем не менее, наличие пользовательской ошибки даст вам больше гибкости для решения
проблем внутри Timer
.
Определение самого Timer
начинается на строке #9. Когда вы впервые создаёте или создаёте
экземпляр объекта из класса, ваш код вызывает специальный метод .__init__()
. В этой
первой версии таймера вы инициализируете только атрибут ._start_time
, который будет
использоваться для отслеживания состояния класса таймера Python. У него будет значение
None, когда таймер ещё не наченал работу. После запуска таймера ._start_time
отслеживает,
когда таймер был запущен.
Примечание: префикс подчеркивания
._start_time
является соглашением Python. Он сигнализирует
о том, что._start_time
является внутренним атрибутом, которым не должны манипулировать
пользователи классаTimer
.
Когда вызывается .start()
для запуска нового таймера Python, сначала проверяется, что таймер
ещё не запущен. Затем сохраняется текущее значение perf_counter()
в ._start_time
. С другой
стороны, когда вызывается .stop()
, вы сначала проверяете, работает ли таймер Python.
Если это так, то вы вычисляете истекшее время как разницу между текущим значением
perf_counter()
и тем, которое было сохранено в ._start_time
. Наконец, сбрасыватся ._start_time
,
чтобы таймер мог быть перезапущен, и распечатать истекшее время.
Использование класса Timer
:
>>> from timer import Timer
>>> t = Timer()
>>> t.start()
>>> t.stop() # Несколько секунд спустя
Elapsed time: 3.8191 seconds
Сравните это с предыдущим примером, где вы использовали perf_counter()
напрямую. Структура кода
довольно похожа, но теперь код стал более понятным, и это является одним из преимуществ
использования классов. Тщательно выбирая имена классов, методов и атрибутов, вы можете
сделать свой код очень информативным!
Использование класса Timer Python
Давайте применим таймер к series_number.py
. Вам нужно всего лишь внести несколько
изменений в свой предыдущий код:
# series_number.py
from timer import Timer
from reader import feed
def main():
"""Печать 1000 элемента последовательности Фибоначчи"""
t = Timer()
t.start()
result = fibo(1000)
t.stop()
print(result)
if __name__ == "__main__":
main()
Обратите внимание, что код очень похож на то, что вы видели ранее. В дополнение к тому,
чтобы сделать код более читабельным, Timer
заботится о печати прошедшего времени на
консоль, что делает логгирование затраченного времени более последовательным.
Когда вы запустите код, вы увидите примерно такой же вывод:
$ python series_number.py
Вычисление заняло 0.0004 секунд
# Функции Python Timer: три способа контролировать ваш код
[ ... Результат вычисления ... ]
Печать прошедшего времени из Timer
может быть последовательной, но данный подход
не очень гибкий. В следующем разделе вы увидите, как настроить свой класс.
Добавление большего удобства и гибкости
До сих пор вы видели, что классы подходят для случаев, когда вы хотите инкапсулировать
состояние и обеспечивать согласованное поведение в вашем коде. В этом разделе вы
добавим больше удобств и гибкости вашему таймеру Python:
- Используем адаптируемый текст и форматирование, сообщая о затраченном времени
- Применим гибкое журналирование либо на экране, либо в файле журнала, либо в других частях вашей программы
- Создать Python таймер, который может накапливаться за несколько вызовов
- Собрать информативное представление таймера Python
Во-первых, давайте посмотрим, как вы можете настроить текст, используемый для отчёта о затраченном
времени. В предыдущем коде текст f"Вычисление заняло {toc - tic:0.4f} секунд"
жёстко закодирован в .stop()
.
Вы можете добавить гибкость классам, используя переменные экземпляра. Их значения обычно
передаются в качестве аргументов .__init__()
и сохраняются как собственные атрибуты. Для удобства
вы также можете предоставить разумные значения по умолчанию.
Чтобы добавить .text
в качестве переменной экземпляра Timer
, изменим код:
Обратите внимание, что текст по умолчанию "Вычисление заняло {toc - tic:0.4f} секунд"
передаётся
как обычная строка, а не как f-строка. Вы не можете использовать f-строку здесь, потому что они
вычисляются немедленно, и когда вы создаете экземпляр Timer, ваш код ещё не вычислил
истекшее время.
Примечание: Если вы хотите использовать f-строку для указания .text, то вам нужно использовать
двойные фигурные скобки, чтобы избежать фигурных скобок, которые заменит фактическое
истекшее время.
В .stop()
используется .text
в качестве шаблона и .format()
для заполнения шаблона:
def stop(self):
"""Остановите таймер и сообщите истекшее время"""
if self._start_time is None:
raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
print(self.text.format(elapsed_time))
После обновления timer.py
вы можете изменить текст следующим образом:
>>> from timer import Timer
>>> t = Timer(text="Вы ждёте {:.1f} секунд")
>>> t.start()
>>> t.stop() # Несколько секунд спустя
Вы ждёте 4.1 секунд
Далее, предположим, что нужно не просто напечатать сообщение в консоль. Может быть, вы хотите
сохранить ваши измерения времени в базе данных. Вы можете сделать это, возвращая значение
elapsed_time
из .stop()
. Затем вызывающий код может либо игнорировать это возвращаемое значение,
либо сохранить его для дальнейшей обработки.
Возможно, вы хотите интегрировать Timer
в свои процедуры логгирования. Для поддержки
логгирования или других выходов из Timer
вам нужно изменить вызов print()
, чтобы пользователь
мог предоставить свою собственную функцию логгирования. Это можно сделать аналогично
тому, как ранее настраивался текст:
def __init__(self, text="Вычисление заняло {:0.4f} секунд", logger=print):
self._start_time = None
self.text = text
self.logger = logger
def stop(self):
"""Остановить таймер и сообщить истекшее время"""
if self._start_time is None:
raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
if self.logger:
self.logger(self.text.format(elapsed_time))
return elapsed_time
Вместо непосредственного использования print()
создана другая переменная экземпляра
self.logger
, которая должна ссылаться на функцию, которая принимает строку в качестве
аргумента. В дополнение к print()
вы можете использовать такие функции, как logging.info()
или .write()
для файловых объектов. Также обратите внимание на тест if
, который
позволяет полностью отключить печать, передав команду logger = None
.
Вот два примера, которые показывают новую функциональность в действии:
>>> from timer import Timer
>>> import logging
>>> t = Timer(logger=logging.warning)
>>> t.start()
>>> t.stop() # Спустя несколько секунд
WARNING:root:Elapsed time: 3.1610 seconds
3.1609658249999484
>>> t = Timer(logger=None)
>>> t.start()
>>> value = t.stop() # Спустя несколько секунд
>>> value
4.710851433001153
Когда вы запускаете эти примеры в интерактивной оболочке, Python автоматически печатает
возвращаемое значение.
Третье улучшение, которое будет добавлено — это возможность накапливать измерения
времени. Это может понадобиться, когда вызывается медленная функция в цикле. Добавим
немного больше функциональности в виде именованных таймеров со словарем, который
отслеживает каждый таймер Python в коде.
Предположим, что файл series_number.py
расширяется до сценария series_numbers.py
,
который загружает и распечатывает несколько результатов работы функции fibo
.
Ниже приведена одна из возможных реализаций:
# series_numbers.py
from timer import Timer
from fibo import fibo
def main():
"""Печать первых 10 элементов последовательности Фибоначчи"""
t = Timer(text="Печать 10 первых элементов за {:0.2f} секунд")
t.start()
for num in range(10):
result = fibo(num)
print(result)
t.stop()
if __name__ == "__main__":
main()
Код перебирает числа от 0 до 9 и использует их в качестве аргументов смещения для fibo()
.
Когда вы запустите скрипт, вы увидите много информации, напечатанной на вашей консоли:
$ python series_numbers.py
[ ... Результаты вычислений ... ]
Одна тонкая проблема с этим кодом заключается в том, что вы измеряете не только время,
необходимое для вычисления элемента последовательности, но и время, которое Python
тратит на печать результатов на экран. Это может быть не так важно, поскольку время,
потраченное на печать, должно быть незначительным по сравнению со временем,
потраченным на вычисления. Тем не менее, было бы хорошо иметь возможность точно
определить время.
Есть несколько способов обойти это без изменения текущей реализации Timer
. Однако
поддержка этого варианта использования будет весьма полезна и может быть выполнена
всего несколькими строками кода.
Во-первых, добавим словарь .timers
в качестве переменной класса в Timer
, что означает,
что все экземпляры Timer
будут использовать его совместно. Реализуется это, определяя
словарь вне любых методов:
class Timer:
timers = dict()
Переменные класса могут быть доступны либо непосредственно в классе, либо через
экземпляр класса:
>>> from timer import Timer
>>> Timer.timers
{}
>>> t = Timer()
>>> t.timers
{}
>>> Timer.timers is t.timers
True
В обоих случаях код возвращает один и тот же пустой словарь классов.
Затем добавим дополнительные имена к вашему таймеру Python. Вы можете использовать
имя для двух разных целей:
- Поиск истекшего времени в вашем коде
- Накопление таймеров с тем же именем
Чтобы добавить имена к вашему таймеру Python, вам нужно внести ещё два изменения
в timer.py
. Во-первых, Timer должен принять имя в качестве параметра. Во-вторых, истекшее
время должно быть добавлено к .timers
, когда таймер останавливается:
class Timer:
timers = dict()
def __init__(
self,
name=None,
text="Вычисление заняло {:0.4f} секунд",
logger=print,
):
self._start_time = None
self.name = name
self.text = text
self.logger = logger
# Добавить новые именованные таймеры в словарь таймеров
if name:
self.timers.setdefault(name, 0)
# Другие методы без изменений
def stop(self):
"""Остановить таймер и сообщить истекшее время"""
if self._start_time is None:
raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
if self.logger:
self.logger(self.text.format(elapsed_time))
if self.name:
self.timers[self.name] += elapsed_time
return elapsed_time
Обратите внимание, что используется .setdefault()
при добавлении нового таймера Python
в .timers
. Это отличная функция, которая устанавливает значение только в том случае, если
имя ещё не определено в словаре. Если имя уже используется в .timers
, то значение
остается без изменений. Это позволяет накапливать несколько таймеров:
>>> from timer import Timer
>>> t = Timer("accumulate")
>>> t.start()
>>> t.stop() # Несколько секунд спустя
Вычисление заняло 3.7036 секунд
3.703554293999332
>>> t.start()
>>> t.stop() # Несколько секунд спустя
Вычисление заняло 2.3449 секунд
2.3448921170001995
>>> Timer.timers
{'accumulate': 6.0484464109995315}
Теперь вернёмся к series_numbers.py
и убедиться, что измеряется только время,
потраченное на вычисления:
# series_numbers.py
from timer import Timer
from fibo import fibo
def main():
"""Печать 10 первых чисел последовательности Фибоначчи"""
t = Timer("download", logger=None)
for num in range(10):
t.start()
result = fibo(num)
t.stop()
print(result)
cum_time = Timer.timers["download"]
print(f"Вычисление 10 чисел последовательности заняло {cum_time:0.2f} секунд")
if __name__ == "__main__":
main()
Повторный запуск сценария верёт такой же результат, как и раньше, хотя сейчас измеряется
только фактическое время вычислений:
$ python series_numbers.py
[ ... Результаты вычислений ... ]
Вычисление 10 чисел последовательности заняло 0.0065 секунд
Последнее улучшение, которое внесём в Timer
— это сделать его более информативным, когда
вы работаете с ним в интерактивном режиме. Попробуем следующее:
>>> from timer import Timer
>>> t = Timer()
>>> t
<timer.Timer object at 0x7f0578804320>
Последняя строка является способом, которым Python представляет объекты по умолчанию. Хотя
вы можете почерпнуть из него некоторую информацию, она обычно не очень полезна. Вместо этого
было бы неплохо увидеть такие вещи, как имя Timer или как он будет сообщать о времени.
В Python 3.7 классы данных (датаклассы) были добавлены в стандартную библиотеку. Они обеспечивают несколько
удобств для ваших классов, включая более информативную строку представления.
Примечание: классы данных включены в Python только для версии 3.7 и выше. Однако в PyPI для Python 3.6 имеется бэкпорт.
Вы можете установить его используя pip:
$ python -m pip install dataclasses
Конвертируем таймер Python в класс данных, используя декоратор @dataclass
. Вы узнаете больше о
декораторах позже в этом уроке. Сейчас можно представить это как о нотации, которая сообщает
Python, что Timer
является классом данных:
from dataclasses import dataclass, field
from typing import Any, ClassVar
@dataclass
class Timer:
timers: ClassVar = dict()
name: Any = None
text: Any = "Вычисление заняло {:0.4f} секунд"
logger: Any = print
_start_time: Any = field(default=None, init=False, repr=False)
def __post_init__(self):
"""Инициализация: добавить таймер к dict таймеров"""
if self.name:
self.timers.setdefault(self.name, 0)
# Остальная часть кода не изменилась
Этот код заменяет предыдущий метод .__init__()
. Обратите внимание, как классы данных используют
синтаксис, который выглядит аналогично синтаксису переменных класса, который вы видели ранее
для определения всех переменных. Фактически, .__init__()
создается автоматически для классов
данных на основе аннотированных переменных в определении класса.
Вы должны аннотировать свои переменные, чтобы использовать класс данных. Вы можете использовать
это, чтобы добавить подсказки к вашему коду. Если вы не хотите использовать подсказки типов, вместо
этого вы можете аннотировать все переменные с помощью Any
, как вы делали выше. Вскоре вы увидите,
как добавить фактические подсказки типов в ваш класс данных.
Вот несколько заметок о классе данных Timer
:
- Строка 6: декоратор
@dataclass
определяетTimer
как класс данных. - Строка 9: Специальная аннотация
ClassVar
необходима для классов данных, чтобы указать, что.timers
является переменной класса. - Строки с 10 по 12:
.name
,.text
и.logger
будут определены как атрибуты наTimer
, значения которых могут быть указаны при создании экземпляровTimer
. Все они имеют заданные значения по умолчанию. - Строка 13: напомним, что
._start_time
— это специальный атрибут, который используется для отслеживания состояния таймера Python, но должен быть скрыт от пользователя. Используяdataclasses.field()
, вы говорите, что._start_time
следует удалить из.__init__()
и представленияTimer
. - Строки с 16 по 19: Используется специальный метод
.__post_init__()
для любой инициализации, которую вам нужно выполнить, кроме установки атрибутов экземпляра. Здесь он используется для добавления именованных таймеров в.timers
.
Новый класс данных Timer
работает так же, как ваш предыдущий обычный класс, за исключением
того, что теперь он имеет хорошее представление:
>>> from timer import Timer
>>> t = Timer()
>>> t
Timer(name=None, text='Elapsed time: {:0.4f} seconds',
logger=<built-in function print>)
>>> t.start()
>>> t.stop() # Несколько секунд спустя
Elapsed time: 6.7197 seconds
6.719705373998295
Теперь у вас есть довольно аккуратная версия Timer
, которая последовательна, гибка, удобна и информативна!
Многие из улучшений, которые вы видели в этом разделе, могут быть применены и к другим типам классов в ваших проектах.
Прежде чем закончить этот раздел, давайте взглянем на полный исходный код Timer
в его нынешнем виде.
Вы заметите добавление подсказок типа к коду для дополнительной документации:
# timer.py
from dataclasses import dataclass, field
import time
from typing import Callable, ClassVar, Dict, Optional
class TimerError(Exception):
"""Пользовательское исключение, используемое для сообщения об ошибках при использовании класса Timer"""
@dataclass
class Timer:
timers: ClassVar[Dict[str, float]] = dict()
name: Optional[str] = None
text: str = "Вычисление заняло {:0.4f} секунд"
logger: Optional[Callable[[str], None]] = print
_start_time: Optional[float] = field(default=None, init=False, repr=False)
def __post_init__(self) -> None:
"""Добавить таймер к dict таймеров после инициализации"""
if self.name is not None:
self.timers.setdefault(self.name, 0)
def start(self) -> None:
"""Начать новый таймер"""
if self._start_time is not None:
raise TimerError(f"Таймер работает. Используйте .stop(), чтобы остановить его")
self._start_time = time.perf_counter()
def stop(self) -> float:
"""Остановить таймер и сообщить истекшее время"""
if self._start_time is None:
raise TimerError(f"Таймер не работает. Используйте .start(), чтобы запустить его")
# Рассчитать прошедшее время
elapsed_time = time.perf_counter() - self._start_time
self._start_time = None
# Сообщить о прошедшем времени
if self.logger:
self.logger(self.text.format(elapsed_time))
if self.name:
self.timers[self.name] += elapsed_time
return elapsed_time
Использование класса для создания таймера, Python предлагает несколько преимуществ:
- Удобочитаемость: ваш код будет читаться более естественно, если вы тщательно выберете имена классов и методов
- Согласованность: ваш код будет легче использовать, если вы инкапсулируете свойства и поведение в атрибуты и методы
- Гибкость: ваш код будет многократно использоваться, если вы будете использовать атрибуты со значениями по умолчанию вместо жестко закодированных значений
Класс очень гибкий, и вы можете использовать его практически в любой ситуации, когда вы хотите отслеживать время,
необходимое для выполнения кода. Тем не менее, в следующих разделах вы узнаете об использовании менеджеров контекста
и декораторов, которые будут более удобными для замеров блоков кода и функций.
Менеджер контекста Python Timer
Python класс Timer
прошёл долгий путь! По сравнению с первым созданным таймером Python код стал достаточно
мощным. Тем не менее, для использования таймера все еще есть немного стандартного кода:
- Во-первых, создать экземпляр класса.
- Вызвать
.start()
перед тем блоком кода, который вы хотите синхронизировать. - Вызовать
.stop()
после блока кода.
К счастью, в Python есть уникальная конструкция для вызова функций до и после
блока кода — менеджер контекста.
В этом разделе вы узнаете, что такое контекстные менеджеры и как вы можете создавать свои собственные.
Затем вы увидите, как расширить Timer, чтобы он мог работать и в качестве менеджера контекста. Наконец, вы увидите, как использование
Timer
в качестве менеджера контекста который упростит ваш код.
Понимание контекстных менеджеров в Python
Менеджеры контекста были частью Python в течение долгого времени. Они были представлены PEP 343 в 2005 году и
впервые реализованы в Python 2.5. Вы можете распознать контекстные менеджеры в коде с помощью ключевого слова with
:
with EXPRESSION as VARIABLE:
BLOCK
EXPRESSION
— это некоторое выражение Python, которое возвращает менеджер контекста.
Менеджер контекста необязательно связан с именем VARIABLE
. BLOCK
— это любой обычный блок кода Python.
Менеджер контекста гарантирует, что ваша программа вызывает некоторый код перед BLOCK
, а другой — после выполнения BLOCK
.
Последнее произойдет, даже если BLOCK
вызовет исключение.
Наиболее распространённое использование контекстных менеджеров, вероятно, обработка различных ресурсов,
такие как файлы, блокировки и соединения с базой данных. Затем менеджер контекста используется для освобождения
и очистки ресурса после его использования. В следующем примере раскрывается фундаментальная структура timer.py
путём печати только строк, содержащих двоеточие. Что ещё более важно, он показывает общую идиому для открытия файла в Python:
>>> with open("timer.py") as fp:
... print("".join(ln for ln in fp if ":" in ln))
...
class TimerError(Exception):
class Timer:
timers: ClassVar[Dict[str, float]] = dict()
name: Optional[str] = None
text: str = "Elapsed time: {:0.4f} seconds"
logger: Optional[Callable[[str], None]] = print
_start_time: Optional[float] = field(default=None, init=False, repr=False)
def __post_init__(self) -> None:
if self.name is not None:
def start(self) -> None:
if self._start_time is not None:
def stop(self) -> float:
if self._start_time is None:
if self.logger:
if self.name:
Обратите внимание, что fp
, указатель файла, никогда не закрывается явно, потому что использовался open()
в качестве менеджера контекста. Можно подтвердить, что fp
закрылся автоматически:
>>> fp.closed
True
В этом примере open(“timer.py”)
— это выражение, которое возвращает менеджер контекста.
Менеджер контекста связан с именем fp
. Менеджер контекста действует во время выполнения print()
.
Данный однострочный кодовый блок выполняется в контексте fp
.
Что это значит, что fp
является контекстным менеджером? Технически это означает, что fp
реализует
протокол менеджера контекста. В основе языка Python лежит много разных протоколов. Вы можете думать о
протоколе как о контракте, в котором указано, какие конкретные методы должен реализовывать ваш код.
Протокол менеджера контекста состоит из двух методов:
- Вызов
.__enter__()
при входе в контекст, связанный с менеджером контекста. - Вызов
.__exit__()
при выходе из контекста, связанного с менеджером контекста.
Другими словами, чтобы создать менеджер контекста самостоятельно, вам нужно написать класс,
который реализует.__enter__()
и .__exit__()
. Ни больше ни меньше. Давайте познкомимся с Hello, World!
примером менеджера контекста:
# greeter.py
class Greeter:
def __init__(self, name):
self.name = name
def __enter__(self):
print(f"Привет {self.name}")
return self
def __exit__(self, exc_type, exc_value, exc_tb):
print(f"Увидимся позже, {self.name}")
Greeter
— менеджер контекста, потому что он реализует протокол менеджера контекста. Вы можете использовать его так:
>>> from greeter import Greeter
>>> with Greeter("Вася"):
... print("Что-нибудь сделать ...")
...
Привет Вася
Что-нибудь сделать ...
Увидимся позже, Вася
Во-первых, обратите внимание, как .__enter__()
вызывается до того, как вы что-то делаете,
а .__exit__()
вызывается после. В этом упрощенном примере мы не ссылаемся на менеджер контекста.
В таких случаях вам не нужно указывать менеджеру контекста имя с as
.
Затем обратите внимание, как .__enter__()
возвращает self
. Возвращаемое значение .__enter__()
— это то,
с чем связано as
. Обычно нужно вернуть self
из .__enter__()
При создании менеджеров контекста.
Вы можете использовать это возвращаемое значение следующим образом:
>>> from greeter import Greeter
>>> with Greeter("Оля") as grt:
... print(f"{grt.name} что-то делает ...")
...
Привет Оля
Оля что-то делает ...
Увидимся позже, Оля
Наконец, .__exit__()
принимает три аргумента: exc_type
, exc_value
и exc_tb
. Они используются
для обработки ошибок в менеджере контекста и отражают возвращаемые значения sys.exc_info()
.
Если во время выполнения блока возникает исключение, то ваш код вызывает .__exit__()
с типом исключения,
экземпляром исключения и объектом трассировки. Часто вы можете игнорировать их в вашем менеджере контекста,
и в этом случае .__exit__()
вызывается перед повторным вызовом исключения:
>>> from greeter import Greeter
>>> with Greeter("Петя") as grt:
... print(f"{grt.age} не существует")
...
Привет Петя
Увидимся позже, Петя
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'Greeter' object has no attribute 'age'
Вы можете увидеть, что «Увидимся позже, Петя» печатается, даже если в коде есть ошибка.
Теперь вы знаете, что такое контекстные менеджеры и как вы можете создать свой собственный.
Если вы хотите погрузиться глубже, то посмотрите
contextlib
в стандартной библиотеке.
Она включает в себя удобные способы определения новых контекстных менеджеров, а также готовые
контекстные менеджеры, которые можно использовать для закрытия объектов,
устранения ошибок или даже бездействия!
Создание менеджера контекста Python Timer
Вы увдели, как работают контекстные менеджеры в целом, но как они могут помочь с Timer
кодом?
Если вы можете запускать определенные функции до и после блока кода, вы можете упростить работу таймера Python.
До сих пор вам нужно было явно вызывать .start()
и .stop()
при синхронизации вашего кода,
но менеджер контекста может сделать это автоматически.
Опять же, чтобы Timer
работал в качестве менеджера контекста, он должен придерживаться протокола менеджера контекста.
Другими словами, он должен реализовывать .__enter__()
и .__exit__()
для запуска и остановки таймера Python.
Все необходимые функции уже доступны, поэтому вам не нужно писать много нового кода.
Просто добавьте следующие методы в класс Timer
:
def __enter__(self):
"""Запустить новый таймер в качестве менеджера контекста"""
self.start()
return self
def __exit__(self, *exc_info):
"""Остановить таймер контекстного менеджера"""
self.stop()
Таймер теперь контекстный менеджер. Важной частью реализации является то, что .__enter__()
вызывает .start()
для
запуска таймера Python при вводе контекста, и .__exit__()
использует .stop()
для остановки таймера Python,
когда код покидает контекст. Проверим это:
>>> from timer import Timer
>>> import time
>>> with Timer():
... time.sleep(0.7)
...
Elapsed time: 0.7012 seconds
Вы также должны отметить ещё две тонкие детали:
__enter__()
возвращаетself
, экземплярTimer
, который позволяет пользователю связать экземплярTimer
с переменной, используяas
. Например,with Timer() as t
: создаст переменнуюt
, указывающую на объектTimer
.__exit__()
ожидает тройку аргументов с информацией о любом исключении, которое произошло во время выполнения контекста. В вашем коде эти аргументы упакованы в кортеж с именемexc_info
и затем игнорируются, что означает, чтоTimer
не будет пытаться обрабатывать какие-либо исключения.
.__exit__()
в этом случае не обрабатывает ошибки. Тем не менее, одна из замечательных особенностей контекстных менеджеров заключается в том, что они гарантированно вызывают .__exit__()
, независимо от того, как выходит контекст. В следующем примере намеренно создадим ошибку путем деления на ноль:
>>> from timer import Timer
>>> with Timer():
... for num in range(-3, 3):
... print(f"1 / {num} = {1 / num:.3f}")
...
1 / -3 = -0.333
1 / -2 = -0.500
1 / -1 = -1.000
Elapsed time: 0.0001 seconds
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
ZeroDivisionError: division by zero
Обратите внимание, что Timer
выводит истекшее время, даже если код не работает. Можно проверять и подавлять ошибки в __exit__()
.
Смотрите документацию для получения дополнительной информации.
Использование менеджера контекста Python Timer
Давайте посмотрим, как использовать менеджер контекста Timer
для определения времени
вычисления числа Фибоначчи. Вспомните, как вы использовали Timer
ранее:
# series_number.py
from timer import Timer
from reader import feed
def main():
"""Печать 1000 элемента последовательности Фибоначчи"""
t = Timer()
t.start()
result = fibo(1000)
t.stop()
print(result)
if __name__ == "__main__":
main()
Теперь замерим вызов fibo(1000)
. Воспользуемся менеджером контекста,
чтобы сделать код короче, проще и более читабельным:
# series_number.py
from timer import Timer
from fibo import fibo
def main():
"""Печать 1000 элемента последовательности Фибоначчи"""
with Timer():
result = fibo(1000)
print(tutorial)
if __name__ == "__main__":
main()
Код делает практически то же, что и код выше. Основное отличие состоит в том, что вы не
определяете постороннюю переменную t
, которая поддерживает чистоту вашего пространства имён.
Запуск скрипта должен дать знакомый результат:
$ python series_number.py
Elapsed time: 0.071 seconds
[ ... Результаты вычисления ... ]
Есть несколько преимуществ для добавления возможностей менеджера контекста к вашему классу таймера Python:
- Незначительные усилия: вам нужно только одну дополнительную строку кода, чтобы рассчитать время выполнения блока кода.
- Удобочитаемость: вызов менеджера контекста удобочитаем, и вы можете более четко визуализировать измеряемый блок кода.
Использование Timer
в качестве менеджера контекста почти так же гибко, как и непосредственное использование .start()
и .stop()
,
хотя в нем меньше стандартного кода. В следующем разделе вы увидите, как можно использовать Timer в качестве декоратора.
Это облегчит мониторинг времени выполнения полных функций.
Python Timer декоратор
Ваш класс Timer
теперь очень универсален. Однако есть один вариант использования, где он может быть ещё более упорядоченным.
Скажем, вы хотите отслеживать время, проведенное внутри одной данной функции в вашей кодовой базе. Используя контекстный менеджер, у вас есть два основных варианта:
1. Использовать Timer
каждый раз, когда вы вызываете функцию:
with Timer("some_name"):
do_something()
Если вы вызовете do_something()
во многих местах, это станет громоздко и сложно в обслуживании.
2. Обернём код функцией содержащей внутри контекстный менеджер:
def do_something():
with Timer("some_name"):
...
Timer
нужно добавить только в одном месте, но это добавляет уровень отступа ко всему определению do_something()
.
Лучшее решение — использовать Timer
в качестве декоратора. Декораторы — это мощные конструкции, которые вы используете для изменения поведения функций и классов. В этом разделе вы узнаете немного о том, как работают декораторы, как можно расширить Timer
, чтобы стать декоратором, и как это упростит функции синхронизации.
Понимание декораторов в Python
Декоратор — это функция, которая оборачивает другую функцию, чтобы изменить её поведение.
Эта техника возможна, потому что функции являются объектами первого класса в Python.
Другими словами, функции можно назначать переменным и использовать в качестве аргументов для других функций,
как и любой другой объект. Это дает вам большую гибкость и является основой для некоторых из более мощных функций Python.
В качестве первого примера создадим декоратор, который ничего не делает:
def turn_off(func):
return lambda *args, **kwargs: None
Во-первых, обратите внимание, что turn_off()
— это обычная функция. Что делает её декоратором, так это то,
что она принимает функцию в качестве единственного аргумента и возвращает функцию.
Вы можете использовать её, чтобы изменить другие функции:
>>> print("Hello")
Hello
>>> print = turn_off(print)
>>> print("Hush")
>>> # Ничего не печатается
Строка print = turn_off(print)
декорирует инструкцию print
с помощью декоратора turn_off()
. По сути, он заменяет print()
на lambda *args, **kwargs: None
не возвращается turn_off()
. Инструкция lambda представляет анонимную функцию, которая ничего не делает, кроме возврата None.
Чтобы определить более интересные декораторы, вам нужно знать о внутренних функциях. Внутренняя функция — это функция, определенная внутри другой функции. Одним из распространенных применений внутренних функций является создание функций фабрик:
def create_multiplier(factor):
def multiplier(num):
return factor * num
return multiplier
multiplier()
является внутренней функцией, определенной внутри create_multiplier()
. Обратите внимание, что у вас есть доступ к factor
внутри multiplier()
, в то время как multiplier()
не определен вне create_multiplier()
:
>>> multiplier
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'multiplier' is not defined
Вместо этого create_multiplier()
используется для создания новых функций умножения, каждая из которых основана на различном factor
:
>>> double = create_multiplier(factor=2)
>>> double(3)
6
>>> quadruple = create_multiplier(factor=4)
>>> quadruple(7)
28
Точно так же вы можете использовать внутренние функции для создания декораторов. Помните, декоратор — это функция, которая возвращает функцию:
def triple(func): #1
def wrapper_triple(*args, **kwargs): #2
print(f"Tripled {func.__name__!r}")
value = func(*args, **kwargs)
return value * 3
return wrapper_triple #6
triple()
является декоратором, потому что это функция, которая ожидает функцию как единственный аргумент, func()
, и возвращает другую функцию, wrapper_triple()
. Обратите внимание на структуру самого triple()
:
- Строка #1 запускает определение
triple()
и ожидает функцию в качестве аргумента. - Строка #2 определяет внутреннюю функцию
wrapper_triple()
. - Строка #6 возвращает
wrapper_triple()
.
Шаблон распространен для определения декораторов. Интересные части — это те, что происходят внутри внутренней функции:
- Строка 2 запускает определение
wrapper_triple()
. Эта функция заменит ту, которую выполняет функцияtriple()
. Параметры*args
и**kwargs
, которые собирают позиционные и ключевые аргументы, которые вы передаете функции. Это дает вам гибкость в использованииtriple()
для любой функции. - В строке 3 выводится имя оформленной функции, и обратите внимание, что к ней применен
triple()
. - Строка 4 вызывает
func()
, функцию, которая была оформлена с помощьюtriple()
. Он передает все аргументы, передаваемые вwrapper_triple()
. - Строка 5 утраивает возвращаемое значение
func()
и возвращает его.
Давайте проверим это! knock()
— это функция, которая возвращает слово World. Посмотрите, что произойдет, если оно утроится:
>>> def knock():
... return "World! "
...
>>> knock = triple(knock)
>>> result = knock()
Tripled 'knock'
>>> result
'World! World! World! '
Умножение текстовой строки на число является формой повторения, поэтому World повторяется три раза. Декорирование происходит при knock = triple(knock)
.
Код кажется немного неуклюжим, чтобы продолжать повторять knock
. Вместо этого в PEP 318 введен более удобный синтаксис для применения декораторов. Следующее определение knock()
делает то же самое, что и выше:
>>> @triple
... def knock():
... return "World! "
...
>>> result = knock()
Tripled 'knock'
>>> result
'World! World! World! '
Символ @
используется для применения декораторов. В этом случае @triple
означает, что triple()
применяется к функции, определенной сразу после нее.
@functools.wraps
— один из немногих декораторов, определенных в стандартной библиотеке. Это очень полезно при определении ваших собственных декораторов. Поскольку декораторы эффективно заменяют одну функцию другой, они создают тонкую проблему с вашими функциями:
>>> knock
<function triple.<locals>.wrapper_triple at 0x7fa3bfe5dd90>
@triple
декорирует knock()
, который затем заменяется внутренней функцией wrapper_triple()
, как подтверждает вывод выше. Это также заменит имя, docstring и другие метаданные. Часто это не будет иметь большого эффекта, но может затруднить интроспекцию.
Иногда декорированные функции должны иметь правильные метаданные. @functools.wraps
исправляют именно эту проблему:
import functools
def triple(func):
@functools.wraps(func)
def wrapper_triple(*args, **kwargs):
print(f"Tripled {func.__name__!r}")
value = func(*args, **kwargs)
return value * 3
return wrapper_triple
С новым определением @triple
метаданные сохраняются:
>>> @triple
... def knock():
... return "World! "
...
>>> knock
<function knock at 0x7fa3bfe5df28>
Обратите внимание, что knock()
теперь сохраняет свое собственное имя, даже после того, как был декорирован.
Это хорошая форма, чтобы использовать @functools.wraps
всякий раз, когда вы определяете декоратор. Схема,
которую вы можете использовать для большинства ваших декораторов, выглядит следующим образом:
import functools
def decorator(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Сделать что-нибудь раньше
value = func(*args, **kwargs)
# Сделать что-нибудь после
return value
return wrapper_decorator
Создание декоратора Timer Python
В этом разделе вы узнаете, как расширить свой таймер Python, чтобы вы также могли использовать его в качестве декоратора. Однако в качестве первого упражнения давайте создадим Python декоратор Timer
с нуля.
Основываясь на приведенной выше схеме, вам нужно только решить, что делать до и после вызова декорированной функции. Это похоже на соображения о том, что делать при входе и выходе из контекстного менеджера. Вы хотите запустить таймер Python перед вызовом декорированной функции и остановить таймер Python после завершения вызова. Декоратор @timer может быть определен следующим образом:
import functools
import time
def timer(func):
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
tic = time.perf_counter()
value = func(*args, **kwargs)
toc = time.perf_counter()
elapsed_time = toc - tic
print(f"Elapsed time: {elapsed_time:0.4f} seconds")
return value
return wrapper_timer
Обратите внимание, насколько wrapper_timer()
напоминает ранний шаблон, установленный вами для замеров кода Python. Вы можете применить @timer
следующим образом:
>>> @timer
... def thousandth_element():
... result = fibo(1000)
... print(result)
...
>>> thousandth_element()
# Python Timer Functions: Вычислить 1000 элемент последовательности
[ ... Результат вычисления ... ]
Elapsed time: 0.5414 seconds
Напомним, что вы также можете применить декоратор к ранее определенной функции:
>>> fibo = timer(fibo)
Поскольку @
применяется при определении функций, в этих случаях необходимо использовать более простую форму.
Одно из преимуществ использования декоратора заключается в том, что вам нужно применить его только один раз,
и он будет каждый раз определять время выполнения функции:
>>> result = fibo(1000)
Elapsed time: 0.5512 seconds
@timer
делает свою работу. Тем не менее, в некотором смысле вы вернулись к исходной точке, поскольку @timer
не
обладает никакой гибкостью или удобством Timer. Можете ли вы также заставить свой класс Timer действовать как декоратор?
До сих пор вы использовали декораторы как функции, применяемые к другим функциям, но это не совсем правильно.
Декораторы должны быть вызываемыми. В Python есть много вызываемых типов. Вы можете сделать свои собственные объекты
вызываемыми, определив специальный объект метод .__call__()
в своем классе. Аналогично ведут себя следующие функции и классы:
>>> def square(num):
... return num ** 2
...
>>> square(4)
16
>>> class Squarer:
... def __call__(self, num):
... return num ** 2
...
>>> square = Squarer()
>>> square(4)
16
Здесь square-это экземпляр, который может быть вызван и может содержать квадрат числа, точно так же, как функция square()
в первом примере.
Это дает вам возможность добавить возможности декоратора к существующему классу таймера:
def __call__(self, func):
"""Поддержка использования Timer в качестве декоратора"""
@functools.wraps(func)
def wrapper_timer(*args, **kwargs):
with self:
return func(*args, **kwargs)
return wrapper_timer
.__call__()
использует тот факт, что Timer
уже является контекстным менеджером, чтобы воспользоваться преимуществами, которые вы уже определили там. Убедитесь, что вы также импортируете functools
в верхней части timer.py
.
Теперь вы можете использовать Timer
в качестве декоратора:
>>> @Timer(text="Вычисление 1000 элемента за {:.4f} секунд")
... def thousandth_element():
... result = fibo(1000)
... print(result)
...
>>> thousandth_element()
[ ... Результат вычисления ... ]
Вычисление 1000 элемента за 0.004 секунд
Прежде чем завершить этот раздел, знайте, что есть более простой способ превратить ваш таймер Python в декоратор. Вы уже видели некоторые сходства между контекстными менеджерами и декораторами. Они оба обычно используются для выполнения чего-то до и после выполнения некоторого заданного кода.
Основываясь на этих сходствах, в стандартной библиотеке есть класс mixin, называемый ContextDecorator
. Вы можете добавить способности декоратора в свои классы контекстного менеджера просто унаследовав ContextDecorator
:
from contextlib import ContextDecorator
class Timer(ContextDecorator):
# Реализация Timer не изменилась
Когда вы используете ContextDecorator
таким образом, нет необходимости реализовывать его.__call__()
самостоятельно, чтобы вы могли безопасно удалить его из класса Timer
.
Использование декоратора таймера Python
Давайте повторим series_number.py
, используя таймер Python в качестве декоратора:
# series_number.py
from timer import Timer
from fibo import fibo
@Timer()
def main():
"""Печать 1000 элемента последовательности Фибоначчи"""
result = fibo(1000)
print(result)
if __name__ == "__main__":
main()
Если вы сравните эту реализацию с оригинальной реализацией без какого-либо времени, то заметите, что единственными различиями
являются импорт Timer
в строке 3 и применение @Timer()
в строке 6. Существенным преимуществом использования декораторов
является то, что они обычно просты в применении, как вы видите.
Тем не менее, декоратор по-прежнему относится ко всей функции. Это означает, что ваш код учитывает время, необходимое для печати результата. Давайте запустим сценарий в последний раз:
$ python series_number.py
[ ... Результат вычисления ... ]
Elapsed time: 0.0069 seconds
Расположение выходных данных прошедшего времени является предательским признаком того, что ваш код также учитывает время, необходимое для печати времени. Как вы видите здесь, ваш код печатает прошедшее время после вычислений.
При использовании таймера в качестве декоратора вы увидите те же преимущества, что и при использовании контекстных менеджеров:
- Малое усилие: вам нужна только одна дополнительная строка кода, чтобы определить время выполнения функции.
- Читабельность: когда вы добавляете декоратор, вы можете более четко отметить, что ваш код будет определять время выполнения функции.
- Согласованность: вам нужно добавить декоратор только тогда, когда функция определена. Ваш код будет последовательно отсчитывать время при каждом вызове.
Однако декораторы не так гибки, как контекстные менеджеры. Вы можете применять их только для выполнения функций. Можно добавить декораторы к уже определенным функциям, но это немного неуклюже и менее распространено.
Код Timer Python
Вы можете использовать код самостоятельно, сохранив его в файле с именем timer.py
и импортировать его в вашу программу. Запустим новый таймер в качестве менеджера контекста:
Кроме этого codetiming.Timer
работает точно так же, как timer.Timer
. Подводя итог, можно использовать Timer
тремя различными способами:
- В виде класса:
t = Timer(name="class")
t.start()
# Сделать что-нибудь
t.stop()
- В виде контекстного менеджера:
with Timer(name="context manager"):
# Сделать что-нибудь
- В виде декоратора:
@Timer(name="decorator")
def stuff():
# Сделать что-нибудь
Этот вид таймера Python в основном полезен для мониторинга времени, которое ваш код тратит на отдельные ключевые
блоки кода или функции. В следующем разделе вы получите краткий обзор альтернатив,
которые можно использовать, если вы хотите оптимизировать свой код.
Другие функции таймеры в Python
Существует множество вариантов замеров выполнения вашего кода Python. В этом уроке вы узнаете, как создать гибкий и удобный класс, который можно использовать несколькими различными способами. Быстрый поиск по PyPI показывает, что уже существует множество проектов, предлагающих решения тайминга Python.
В этом разделе вы сначала узнаете больше о различных функциях, доступных в стандартной библиотеке для измерения времени, и о том, почему perf_counter()
предпочтительнее. Затем вы увидите альтернативы оптимизации вашего кода, для которых таймер не очень хорошо подходит.
Использование альтернативных функций таймеров в Python
Вы использовали perf_counter()
на протяжении всего этого урока для выполнения фактических измерений времени, но библиотека time Python поставляется с несколькими другими функциями, которые также измеряют время. Вот некоторые альтернативы:
time()
perf_counter_ns()
monotonic()
process_time()
Одна из причин, почему существует несколько функций, заключается в том, что Python представляет время как float.
Числа с плавающей запятой по своей природе неточны. Возможно, вы уже видели подобные результаты раньше:
>>> 0.1 + 0.1 + 0.1
0.30000000000000004
>>> 0.1 + 0.1 + 0.1 == 0.3
False
Float Python следует стандарту IEEE 754 для арифметики с плавающей запятой, который пытается представить все числа
с плавающей запятой в 64 битах. Поскольку существует бесконечно много чисел с плавающей запятой, вы не можете выразить их в виде конечного числа битов.
IEEE 754 предписывает систему, в которой плотность чисел, которые вы можете представить, изменяется.
Чем ближе вы к 1, тем больше чисел вы можете представить. Для больших чисел есть больше пространства между числами,
которые вы можете выразить. Это имеет некоторые последствия, когда вы используете float для представления времени.
Рассмотрим time()
. Основная цель этой функции-представить реальное время прямо сейчас. Он делает это как число секунд,
прошедших с данного момента времени, называемого эпохой. Число, возвращаемое функцией time()
, довольно велико,
а это означает, что доступных чисел становится меньше, и разрешение страдает. В частности, time()
не
способен измерять наносекундные различия:
>>> import time
>>> t = time.time()
>>> t
1564342757.0654016
>>> t + 1e-9
1564342757.0654016
>>> t == t + 1e-9
True
Наносекунда — это одна миллиардная секунды. Обратите внимание, что добавление наносекунды к t
не влияет на результат.
perf_counter()
, с другой стороны, использует некоторый неопределенный момент времени в качестве своей эпохи,
что позволяет ему работать с меньшими числами и, следовательно, получать лучшее разрешение:
>>> import time
>>> p = time.perf_counter()
>>> p
11370.015653846
>>> p + 1e-9
11370.015653847
>>> p == p + 1e-9
False
Здесь вы видите, что добавление наносекундного числа на самом деле влияет на результат.
Проблемы с представлением времени в виде float хорошо известны, поэтому Python 3.7 ввел новую опцию.
Каждая функция измерения времени теперь имеет соответствующую функцию _ns
, которая возвращает число
наносекунд в виде int вместо числа секунд в виде float. Например, time()
теперь имеет наносекундный аналог time_ns()
:
>>> import time
>>> time.time_ns()
1564342792866601283
Целые числа в Python не ограничены, поэтому это позволяет time_ns()
давать разрешение наносекунды на всю вечность. Аналогично, perf_counter_ns()
— это наносекундный вариант perf_counter()
:
>>> import time
>>> time.perf_counter()
13580.153084446
>>> time.perf_counter_ns()
13580765666638
Поскольку perf_counter()
уже обеспечивает наносекундное разрешение, у использования perf_counter()
меньше преимуществ.
Примечание:
perf_counter_ns()
доступен только в Python 3.7 и более поздних версиях. В этом уроке вы использовалиperf_counter()
в своем классе Timer. Таким образом, таймер можно использовать и в более старых версиях Python.
Для получения дополнительной информации о функциях_ns в time
ознакомьтесь с новыми классными функциями в Python 3.
Есть две функции во time, которые не измеряют время, проведенное во сне (sleeping). Это process_time()
и thread_time()
, которые полезны в некоторых настройках. Однако для таймера обычно требуется измерить полное затраченное время. Последняя функция в приведенном выше monotonic()
. Название намекает на то, что эта функция является монотонным таймером, который является таймером Python, который никогда не может двигаться назад.
Все эти функции монотонны, за исключением time()
, который может вернуться назад, если системное время отрегулировано. В некоторых системах monotonic()
— это та же функция, что и perf_counter(), и вы можете использовать их взаимозаменяемо. Однако это не всегда так. Вы можете использовать time.get_clock_info()
для получения дополнительной информации о функции таймера Python. Используя Python 3.7 В Linux я получаю следующую информацию:
>>> import time
>>> time.get_clock_info("monotonic")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
monotonic=True, resolution=1e-09)
>>> time.get_clock_info("perf_counter")
namespace(adjustable=False, implementation='clock_gettime(CLOCK_MONOTONIC)',
monotonic=True, resolution=1e-09)
Результаты могут быть разными в вашей системе.
PIP 418 описывает некоторые обоснования введения этих функций. Она включает в себя следующие краткие описания:
time.monotonic()
: тайм-аут и планирование, на которые не влияют обновления системных часовtime.perf_counter()
: бенчмаркинг, самые точные часы за короткий периодtime.process_time()
: профилирование, процессорное время процесса
Как вы можете видеть, обычно это лучший выбор для вас, чтобы использовать perf_counter()
для вашего таймера Python.
Оценка времени работы со временем timeit
Допустим, нужно выжать из кода последний бит производительности и задаетесь вопросом о наиболее эффективном
способе преобразования списка в множество. Вы хотите сравнить, использование set() и литерал
множества {…}. Для этого вы можете использовать свой таймер Python:
Этот тест, по-видимому, указывает на то, что литерал множества может быть немного быстрее.
Однако эти результаты довольно неопределенные, и если вы повторно запустите код, можно получить
совершенно другие результаты. Это потому, что вы только один раз пробуете код. Например,
вам может не повезти, и вы можете запустить сценарий как раз в тот момент, когда ваш
компьютер будет занят другими задачами.
Лучше всего воспользоваться стандартной библиотекой. Она предназначен именно для измерения времени
выполнения небольших фрагментов кода. Для этого импортируем и вызовем timeit.timeit()
из
Python как обычную функцию в интерфейсе командной строки. Вы можете рассчитать эти два варианта следующим образом:
$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "set(nums)"
2000000 loops, best of 5: 163 nsec per loop
$ python -m timeit --setup "nums = [7, 6, 1, 4, 1, 8, 0, 6]" "{*nums}"
2000000 loops, best of 5: 121 nsec per loop
timeit он автоматически вызывает ваш код много раз, чтобы усреднить шумные измерения. Результаты timeit подтверждают, что литерал set работает быстрее, чем set()
.
Примечание: будьте осторожны, когда вы используете timeit на коде, который может загружать файлы или получать доступ к базам данных. Поскольку время от времени он автоматически вызывает вашу программу несколько раз, вы можете непреднамеренно в конечном итоге заспамить сервер запросами!
Наконец, интерактивная оболочка IPython и Jupyter notebook имеют дополнительную поддержку этой функции с помощью команды %timeit magic
:
In [1]: numbers = [7, 6, 1, 4, 1, 8, 0, 6]
In [2]: %timeit set(numbers)
171 ns ± 0.748 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
In [3]: %timeit {*numbers}
147 ns ± 2.62 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Опять же, измерения показывают, что использование литерала множества происходит быстрее.
Поиск узких мест в коде с помощью профилирования
timeit
отлично подходит для бенчмаркинга конкретного фрагмента кода. Однако было бы очень громоздко использовать его для проверки всех частей вашей программы и определения того, какие разделы занимают больше всего времени. Вместо этого можно использовать профилировщик.
cProfile — это профилировщик, к которому вы можете получить доступ в любое время из стандартной библиотеки. Вы можете использовать его несколькими способами, хотя обычно проще всего использовать его в качестве инструмента командной строки:
$ python -m cProfile -o series_number.prof series_number.py
Команда выполняет series_number.py
с включенным профилированием. Вы сохраняете выходные данные из cProfile в series_number.prof
как указано в параметре -o
. Выходные данные находятся в двоичном формате, который нуждается в специальной программе, чтобы понять его. Опять же, Python содержит эту опцию прямо в стандартной библиотеке! Запуск модуля pstats на вашем компьютере с .prof
файлом, открывает интерактивный браузер статистики профиля:
$ python -m pstats series_number.prof
Welcome to the profile statistics browser.
series_number.prof% help
Documented commands (type help <topic>):
========================================
EOF add callees callers help quit read reverse sort stats strip
Чтобы использовать pstats
, вы вводите команды в командной строке. Здесь вы можете увидеть интегрированную справочную систему. Обычно используются команды sort
и stats
. Чтобы получить более очищенный вывод, может быть полезна strip
:
series_number.prof% strip
series_number.prof% sort cumtime
series_number.prof% stats 10
Sun Jun 28 23:02:00 2020 series_number.prof
165 function calls (164 primitive calls) in 0.002 seconds
Ordered by: cumulative time
List reduced from 85 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
2/1 0.000 0.000 0.002 0.002 {built-in method builtins.exec}
1 0.000 0.000 0.002 0.002 series_number.py:3(<module>)
1 0.000 0.000 0.001 0.001 series_number.py:7(main)
1 0.000 0.000 0.001 0.001 <frozen importlib._bootstrap>:986(_find_and_load)
1 0.000 0.000 0.001 0.001 <frozen importlib._bootstrap>:956(_find_and_load_unlocked)
1 0.001 0.001 0.001 0.001 fibo.py:1(fibo)
2 0.001 0.000 0.001 0.000 {built-in method builtins.print}
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:650(_load_unlocked)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:777(exec_module)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap>:890(_find_spec)
Этот вывод показывает, что общее время выполнения составило 0.002 секунды. В нем также перечислены десять функций, на которые ваш код потратил большую часть своего времени. Здесь вы отсортированы по кумулятивному времени (cumtime), что означает, что ваш код считает время, когда данная функция вызвала другую функцию.
Вы можете видеть, что ваш код проводит практически все свое время внутри модуля series_number
, и в частности, внутри fibo()
. Хотя это может быть полезным подтверждением того, что вы уже знаете, часто гораздо интереснее узнать, где ваш код на самом деле проводит время.
Столбец общее время (tottime) показывает, сколько времени ваш код провел внутри функции, исключая время в подфункциях. Вы можете видеть, что ни одна из вышеперечисленных функций на самом деле не тратит на это никакого времени. Чтобы найти, где код провел большую часть своего времени, выполните другую команду sort
:
Welcome to the profile statistics browser.
series_number.prof% sort tottime
series_number.prof% stats 10
Sun Jun 28 23:21:52 2020 series_number.prof
165 function calls (164 primitive calls) in 0.002 seconds
Ordered by: internal time
List reduced from 85 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 0.001 0.001 C:Python38workfibo.py:1(fibo)
2 0.001 0.000 0.001 0.000 {built-in method builtins.print}
3 0.000 0.000 0.000 0.000 {built-in method nt.stat}
1 0.000 0.000 0.000 0.000 {built-in method io.open_code}
1 0.000 0.000 0.000 0.000 {method 'read' of '_io.BufferedReader' objects}
1 0.000 0.000 0.001 0.001 series_number.py:7(main)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:969(get_data)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:578(_compile_bytecode)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:849(get_code)
1 0.000 0.000 0.000 0.000 <frozen importlib._bootstrap_external>:1431(find_spec)
Теперь видно, что series_number.py
фактически большую часть своего времени проводит работая с модулем fibo.py
. Последнее является одной из зависимостей скрипта, который используется для вычисления.
Вы можете использовать статистику, чтобы получить некоторое представление о том, где ваш код тратит большую часть своего времени, и посмотреть, сможете ли вы оптимизировать любые узкие места, которые вы найдете. Вы также можете использовать этот инструмент, чтобы лучше понять структуру вашего кода. Например, вызываемые и вызывающие команды покажут вам, какие функции вызывают и вызываются данной функцией.
Вы также можете исследовать некоторые функции. Давайте посмотрим, сколько накладных расходов вызывает timer
, фильтруя результаты с помощью фразы timer
:
series_number.prof% stats timer
1393801 function calls (1389027 primitive calls) in 0.586 seconds
Ordered by: internal time
List reduced from 1443 to 8 due to restriction <'timer'>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.000 0.000 timer.py:13(Timer)
1 0.000 0.000 0.000 0.000 timer.py:35(stop)
1 0.000 0.000 0.003 0.003 timer.py:3(<module>)
1 0.000 0.000 0.000 0.000 timer.py:28(start)
1 0.000 0.000 0.000 0.000 timer.py:9(TimerError)
1 0.000 0.000 0.000 0.000 timer.py:23(__post_init__)
1 0.000 0.000 0.000 0.000 timer.py:57(__exit__)
1 0.000 0.000 0.000 0.000 timer.py:52(__enter__)
К счастью, таймер вызывает только минимальные накладные расходы. Используйте quit, чтобы выйти из браузера pstats
, когда вы закончите исследование.
Для получения более мощного интерфейса для анализа данных профиля, запустите программу KCacheGrind. Он использует свой собственный формат данных, но вы можете конвертировать данные из профиля с помощью pyprof2calltree
:
$ pyprof2calltree -k -i series_number.prof
Команда преобразует series_number.prof
, затем открывая программу для анализа данных.
Последний вариант, который вы увидите здесь для замеров вашего кода — это line_profiler.cProfile
может сказать вам, какие функции ваш код тратит больше всего времени, но он не даст вам понять, какие строки внутри этой функции являются самыми медленными. Вот где line_profiler
может вам помочь.
Примечание: Вы также можете профилировать потребление памяти вашего кода. Это выходит за рамки данного руководства. Однако вы можете взглянуть на memory-profiler, если вам нужно контролировать потребление памяти вашими программами.
Обратите внимание, что line_profiler
требует времени и добавляет изрядную часть накладных расходов к вашей среде выполнения. Более стандартный рабочий процесс заключается в том, чтобы сначала использовать cProfile для определения того, какие функции нужно просмотреть, а затем запустить line_profiler
для этих функций.line_profiler
не является частью стандартной библиотеки, поэтому вы должны сначала следовать инструкциям по установке, чтобы настроить его.
Перед запуском профилировщика необходимо указать ему, какие функции следует профилировать. Это выполняется, добавлением декоратора @profile
в свой исходный код. Например, для профилирования Timer.stop() вы добавляете следующее в timer.py
:
@profile
def stop(self) -> float:
# Остальная часть кода не изменилась
Обратите внимание, что вы нигде не импортируете profile
. Вместо этого он автоматически добавляется в глобальное пространство имен при запуске профилировщика. Вы должны удалить строку, когда закончите профилирование. В противном случае вы получите NameError.
Затем запустим профилировщик, используя kernprof
, который является частью пакета line_profiler
:
$ kernprof -l series_number.py
Команда автоматически сохраняет данные профилировщика в файле series_number.py.lprof
. Вы можете увидеть эти результаты, использу модуль line_profiler
:
$ python -m line_profiler series_number.py.lprof
Timer unit: 1e-06 s
Total time: 1.6e-05 s
File: /home/user/timer.py
Function: stop at line 35
# Hits Time PrHit %Time Line Contents
=====================================
35 @profile
36 def stop(self) -> float:
37 """Остановить таймер и сообщить истекшее время"""
38 1 1.0 1.0 6.2 if self._start_time is None:
39 raise TimerError(f"Timer is not running. ...")
40
41 # Calculate elapsed time
42 1 2.0 2.0 12.5 elapsed_time = time.perf_counter() - self._start_time
43 1 0.0 0.0 0.0 self._start_time = None
44
45 # Report elapsed time
46 1 0.0 0.0 0.0 if self.logger:
47 1 11.0 11.0 68.8 self.logger(self.text.format(elapsed_time))
48 1 1.0 1.0 6.2 if self.name:
49 1 1.0 1.0 6.2 self.timers[self.name] += elapsed_time
50
51 1 0.0 0.0 0.0 return elapsed_time
Во-первых, обратите внимание, что единица времени в этом отчете составляет микросекунда (1e-06 с). Обычно наиболее доступное число для просмотра — это %Time, которое показывает процент от общего времени, которое ваш код проводит внутри функции в каждой строке. В этом примере вы можете видеть, что ваш код тратит почти 70% времени на строке 47, которая является строкой, которая форматирует и печатает результат таймера.
Вывод
В этом руководстве вы увидели несколько разных подходов к добавлению таймера Python в свой код:
- Вы использовали класс, чтобы сохранить состояние и добавить удобный интерфейс. Классы очень гибкие, и использование
Timer
напрямую дает вам полный контроль над тем, как и когда вызывать таймер. - Вы использовали менеджер контекста для добавления функций в блок кода и, при необходимости, для последующей очистки. Менеджеры контекста просты в использовании, а добавление с помощью Timer() может помочь вам более четко различать код визуально.
- Вы использовали декоратор, чтобы добавить поведение к функции. Декораторы лаконичны и убедительны, а использование
@Timer()
— это быстрый способ отслеживать время выполнения вашего кода.
Вы также поняли, почему при тестировании кода предпочитаете time.perf_counter()
, а не time.time()
, а также
какие другие альтернативы полезны при оптимизации кода.
Теперь вы можете добавить функции Timer
Python в свой собственный код!
Отслеживание скорости выполнения вашей программы в журналах поможет вам отслеживать ваши сценарии.
Python – язык программирования, который можно отнести к общему назначению. С его помощью пишут как бизнес-софт, так и развлекательный (игровой) контент. Это отличное решение для новичков в разработке. Относится к объектно-ориентированному типу.
В данной статье будет рассказано о том, что собой представляет задержка в Python, как использовать time (таймер), для чего все это нужно. Информация пригодится даже опытным разработчикам, которые планируют работу со временем в будущей утилите.
Ключевые термины
Пытаясь освоить Python, программеру потребуется выучить немало теории. Вот базовые термины, без которых время и остальные компоненты кодификаций применять на деле не получится:
- Ключевое слово – зарезервированное системой слово или фраза. Обозначает действие, операцию, функцию. Ключевики не могут выступать в виде имен переменных.
- Переменная – именованная ячейка памяти, которую можно изменять, сохранять и считывать.
- Алгоритм – последовательность действий, набор правил, помогающих решать те или иные задачи.
- Класс – набор связанных между собой объектов, которые имеют общие свойства.
- Объект – комбинация переменных, констант и иных структурных единиц. Они выбираются совместно и аналогичным образом проходят обработку.
- Константа – значение, которое не будет меняться на протяжении всего выполнения утилиты.
- Тип данных – классификация информации определенного вида.
- Массив – множество данных. Они предварительно группируются.
Огромную роль в Python играют ключевые слова. Их необходимо либо запоминать, либо заучивать, либо держать где-то поблизости справочник с соответствующими данными. Иначе при объявлении переменных не исключены проблемы.
Задержка – это…
Задержка – термин, который применим ко времени. Он встречается и в обыденной жизни. Это – ситуация, когда что-то происходит или должно осуществиться не сразу. Пример – после наступления каких-то обстоятельств.
В программировании задержка «откладывает» выполнение кода на определенное время. Часто такая потребность возникает тогда, когда нужно дождаться завершения иного процесса, чтобы задействовать далее полученный результат.
При рассмотрении многопоточных утилит, использовать таймер (timer) и время (time) нужно, чтобы дождаться завершения операции и функций из других потоков.
Класс Timer
Класс Timer () в Python отвечает за время и работу с ним «от начала по конца». Модуль, метод, используемый для задержки и всего, что с ней связано. Перед использованием оного требуется произвести импорт компонента.
Для этого подойдет запись типа import time в Python. Класс относится к модулю threading. Он создает таймер, который запускает функцию с аргументами и ключевыми значениями (kwargs). Происходит это за счет time, установленного как interval. Этот параметр указывается в секундах.
Программеру предстоит запомнить следующее:
- Запись функции с классом, отвечающего за таймаут (timeout) –
- Если args равен None (этот показатель устанавливается изначально), Python использует пустой список.
- Когда ключевое слово kwargs равен None, применяется пустой словарь.
- Класс «Таймер» представлен действием, которое нужно запускать только по прошествии конкретного промежутка времени.
- Таймер выступает в виде подкласса threading.Thread().
Все это требуется запомнить. А еще – учесть, что в процессе коддинга предстоит использовать суперкласс (super class), а также мета данные.
Функции
Рассматривая methods time, программисту требуется изучить разнообразные функции, связанные со временем. Это поможет лучше разобраться в потоках и задержках. Не стоит забывать, что при тестинге важно использовать print. Эта операция выводит результат на экран.
Time.Time
Функция Time() будет возвращать число секунд, которые прошли с начала эпохи. Для Unix-систем это – 1.01.1970. Отсчет с 12 часов ночи ровно.
Ctime()
Компонент, который будет в виде аргумента в Python принимать количество секунд, прошедших с самого начала эпохи. Результат – возврат строки по местному time.
Sleep
Отвечает за непосредственную задержку. Откладывает исполнение нынешнего потока на заданное количество секунд.
Класс struct_time
Изучая, какой метод подойдет для работы с таймерами и super class, стоит обратить внимание на struct_time. Этот объект может быть принят некоторыми функциями в упомянутом ранее модуле. При обработке оного происходит возврат.
Выше – наглядный пример.
Реализация Sleep
Когда нужный метод для работы с задержкой изучен, можно рассмотреть то, как сделать таймаут. Для этого используют super class, а также sleep. Он проходит реализацию несколькими способами:
- Через time.sleep(). Это – встроенная возможность Python. Отвечает за таймаут через модуль time. Откладывает выполнение потока на установленное количество секунд.
- Вызов с декораторами. Активируют, когда одно неудачно выполненное действие требуется запустить снова.
- В потоках. Такие ситуации требуют, чтобы приложение избегало простоя. Для этого применяют или time.sleep(), или Event.wait() из модуля threading.
- Из Async IO. Асинхронные возможности появились в Питоне, начиная с 3.4 версии. Это – тип параллельного программирования.
- В Tkinter и wxPython. Отсрочки возможны при создании пользовательских интерфейсов. При применении sleep() внутри GUI кода блокируется цикл обработки событий.
- After(). Это – метод, который погружает в сон для Tkinter. Часть стандартной библиотеки.
- CallLater. Метод для wxPython. Имеет больше виджетов и хорошо годится для нативной разработки.
А вот видео, где можно наглядно увидеть работу с таймером в Python. Лучше разобраться с этой темой, как и с языком программирования, помогут дистанционные компьютерные курсы. Программы рассчитаны на срок до года. В конце будет выдан электронный сертификат. В процессе пользователи получат не только хорошо поданный учебный материал, но и новые полезные связи. А еще – соберут портфолио для трудоустройства.
Ok, I’ll start with why your timer is lagging.
What happens in your program is that the time.sleep()
call «sleeps» the program’s operation for 1 second, once that second has elapsed your program begins execution again. But your program still needs time to execute all the other commands you’ve told it to do, so it takes 1s + Xs
to actually perform all the operations. Although this is a very basic explanation, it’s fundamentally why your timer isn’t synchronous.
As for why you’re constantly printing on a new line, the print()
function has a pre-defined end of line character that it appends to any string it is given.
print(value, ..., sep=' ', end='n', file=sys.stdout, flush=False)
You can overwrite this with anything by putting end="YourThing"
in your print statement like so
for x in range(3):
print("Test", end="")
The above example appends an empty string to the end of the line, so the output of the loop would be
"TestTestTest"
As for solving your timer problem, you should use something similar to
timePoint = time.time()
while True:
#Convert time in seconds to a gmtime struct
currentTime = time.gmtime(time.time() - timePoint))
#Convert the gmtime struct to a string
timeStr = time.strftime("%M minutes, %S seconds", currentTime)
#Print the time string
print(timeStr, end="")
Download Article
Download Article
If you’re just getting started with Python, making a countdown time is a great way to exercise your programming skills. We’ll show you how to write a Python 3 program that counts down from any number, all the way down to zero.
Steps
-
1
Open your text editor or IDE. On Windows, the easiest option is to use IDLE, which is installed together with Python.
-
2
Open a new file. In many text editors, you can do this by going to the file menu and click on New Window or by just pressing Ctrl+N.
Advertisement
-
3
Import the
time
module. Thetime
contains many Python functions related to time, for example getting the current time or waiting a specified amount of time (the latter is what you will need for this program). To import the module, type: -
4
Define a countdown function. You can give the function any name you want, but usually you should use something descriptive. In this case, you could name it countdown(). Add the following code:
-
5
Write a while-loop. A while-loop repeats the code inside it as long as its condition is true. In this case, you want the countdown to continue until the number reaches 0. So, you need to write:
- Notice the spaces at the beginning of the line. These tell Python that this line of code is part of the definition of the
countdown
function, and not just some code below it. You can use any number of spaces, but you need to use the same amount before any line that you want to indent once. - You will need to indent the next code lines twice, because they are both part of the function definition and part of the while-loop. This is done by using twice as many spaces.
- Notice the spaces at the beginning of the line. These tell Python that this line of code is part of the definition of the
-
6
Print the current number. This does not mean using a printer to get it on paper, «printing» is a word that means «displaying on the screen». This will let you see how far the countdown has progressed.
-
7
Count down the number. Make it 1 less. This is done with the following code:
Alternatively, if you don’t want to type so much, you can instead write:
-
8
Make the program wait a second. Otherwise, it would be counting down the numbers way too fast and the countdown would be finished before you could even read it. For waiting a second, use the
sleep
function of thetime
module that you had previously imported: -
9
Do something when the countdown reaches zero. To print out «BLAST OFF!» when the countdown reaches zero, add this line:
- Note that this line is only indented once. This is because it is no longer part of the while-loop. This code is only run after the while-loop finishes.
-
10
Ask the user from which number to start the countdown. This will give your program some flexibility, instead of always counting from the same number.
- Print the question to the user. They need to know what they are supposed to enter.
print("How many seconds to count down? Enter an integer:")
- Get the answer. Store the answer in a variable so that you can do something with it later.
- While the user’s answer is not an integer, ask the user for another integer. You can do this with a while-loop. If the first answer is already an integer, the program will not enter the loop and just proceed with the next code.
while not seconds.isdigit(): print("That wasn't an integer! Enter an integer:") seconds = input()
- Now you can be sure that the user entered an integer. However, it is still stored inside a string (
input()
always returns a string, because it can’t know whether the user will enter text or numbers). You need to convert it to an integer:If you would have tried to convert a string whose content isn’t an integer into an integer, you would get an error. This is the reason while the program checked whether the answer was actually an integer first.
- Print the question to the user. They need to know what they are supposed to enter.
-
11
Call the
countdown()
function. You had previously defined it, but defining a function doesn’t do what is written inside it. To actually run the countdown code, call thecountdown()
function with the number of seconds that the user inputted: -
12
Check your finished code. It should look like this:
import time def countdown(t): while t > 0: print(t) t -= 1 time.sleep(1) print("BLAST OFF!") print("How many seconds to count down? Enter an integer:") seconds = input() while not seconds.isdigit(): print("That wasn't an integer! Enter an integer:") seconds = input() seconds = int(seconds) countdown(seconds)
- The empty lines are only there to make the code easier to read. They are not required, and Python actually ignores them.
- You can write t = t - 1 instead of t -= 1 if you prefer.
Advertisement
Add New Question
-
Question
How do I get it to print at each second rather than having it all print at once?
Use the time.sleep(x) function. It allows for the program to pause for x seconds. After every print statement, insert time.sleep(1).
-
Question
How do I make the font larger in Python on a Mac?
In the Python shell, click Options, Configure, Idle. From there, you can change the font size.
-
Question
Why have the ‘time’ module if it is never used?
If you write a program for, say, a robot and have the servo controls in milliseconds, then it will use the time module to send the electrical signal for the right amount of time.
See more answers
Ask a Question
200 characters left
Include your email address to get a message when this question is answered.
Submit
Advertisement
Thanks for submitting a tip for review!
About This Article
Thanks to all authors for creating a page that has been read 185,542 times.