Python sleep (): как добавить задержки в ваш код

Python sleep (): как добавить задержки в ваш код

Вам когда-нибудь нужно было заставить свою программу Python чего-то ждать? В большинстве случаев вы хотите, чтобы ваш код выполнялся как можно быстрее. Но бывают случаи, когда время нахождения кода на некоторое время действительно отвечает вашим интересам.

Например, вы можете использовать вызов Pythonsleep() для имитации задержки в вашей программе. Возможно, вам нужно подождать, пока файл будет загружен или загружен, или изображение будет загружено или выведено на экран. Возможно, вам даже понадобится сделать паузу между вызовами веб-API или между запросами к базе данных. Добавление вызовов Pythonsleep() в вашу программу может помочь в каждом из этих и многих других случаев!

В этом руководстве вы узнаете, как добавить вызовы Pythonsleep() с помощью:

  • time.sleep()

  • Декораторы

  • Потоки

  • Async IO

  • Графические пользовательские интерфейсы

Эта статья предназначена для разработчиков среднего уровня, которые хотят расширить свои знания Python. Если это звучит как вы, тогда давайте начнем!

Free Bonus:Get our free "The Power of Python Decorators" guide, который показывает вам 3 продвинутых шаблона декоратора и техники, которые вы можете использовать для написания более чистых и дополнительных программ Pythonic.

Добавление вызова Pythonsleep() сtime.sleep()

Python имеет встроенную поддержку для перевода вашей программы в спящий режим. В модулеtime есть функцияsleep(), которую вы можете использовать для приостановки выполнения вызывающего потока на указанное вами количество секунд.

Вот пример использованияtime.sleep():

>>>

>>> import time
>>> time.sleep(3) # Sleep for 3 seconds

Если вы запустите этот код в своей консоли, то у вас должна возникнуть задержка, прежде чем вы сможете ввести новый оператор в REPL.

Note: В Python 3.5 разработчики ядра немного изменили поведениеtime.sleep(). Новый системный вызов Pythonsleep() будет длиться по крайней мере указанное вами количество секунд, даже если сон прерывается сигналом. Однако это не применяется, если сам сигнал вызывает исключение.

Вы можете проверить, как долго длится сон, используя модуль Pythontimeit:

$ python3 -m timeit -n 3 "import time; time.sleep(3)"
3 loops, best of 3: 3 sec per loop

Здесь вы запускаете модульtimeit с параметром-n, который сообщаетtimeit, сколько раз выполнять следующий оператор. Вы можете видеть, чтоtimeit выполнил оператор 3 раза и что лучшее время выполнения было 3 секунды, что и ожидалось.

По умолчаниюtimeit запускает ваш код один миллион раз. Если бы вы запускали приведенный выше код со значением по умолчанию-n, то через 3 секунды на итерацию ваш терминал зависал бы примерно на 34 дня! Модульtimeit имеет несколько других параметров командной строки, которые вы можете проверить в егоdocumentation.

Давайте создадим что-то более реалистичное. Системный администратор должен знать, когда один из их веб-сайтов выходит из строя. Вы хотите регулярно проверять код состояния веб-сайта, но не можете постоянно запрашивать веб-сервер, иначе это повлияет на производительность. Один из способов выполнить эту проверку - использовать системный вызов Pythonsleep():

import time
import urllib.request
import urllib.error

def uptime_bot(url):
    while True:
        try:
            conn = urllib.request.urlopen(url)
        except urllib.error.HTTPError as e:
            # Email admin / log
            print(f'HTTPError: {e.code} for {url}')
        except urllib.error.URLError as e:
            # Email admin / log
            print(f'URLError: {e.code} for {url}')
        else:
            # Website is up
            print(f'{url} is up')
        time.sleep(60)

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

Здесь вы создаетеuptime_bot(), который принимает URL-адрес в качестве аргумента. Затем функция пытается открыть этот URL с помощьюurllib. Если естьHTTPError илиURLError, то программа улавливает их и выводит ошибку. (В реальной среде вы могли бы зарегистрировать ошибку и, возможно, отправить электронное письмо веб-мастеру или системному администратору.)

Если ошибок не возникает, то ваш код распечатывает, что все хорошо. Независимо от того, что произойдет, ваша программа будет спать 60 секунд. Это означает, что вы заходите на сайт только раз в минуту. URL, используемый в этом примере, неверен, поэтому он будет выводить на вашу консоль каждую минуту:

HTTPError: 404 for http://www.google.com/py

Идите вперед и обновите код, чтобы использовать заведомо исправный URL, напримерhttp://www.google.com. Затем вы можете повторно запустить его, чтобы увидеть, как он работает успешно. Вы также можете попробовать обновить код, чтобы отправить электронное письмо или зарегистрировать ошибки. Для получения дополнительной информации о том, как это сделать, ознакомьтесь сSending Emails With Python иLogging in Python.

Добавление вызова Pythonsleep() с помощью декораторов

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

Другой случай использования, который я лично испытал, - это когда мне нужно проверять состояние пользовательского интерфейса во время автоматического тестирования. Пользовательский интерфейс может загружаться быстрее или медленнее, чем обычно, в зависимости от компьютера, на котором я запускаю тест. Это может изменить то, что отображается на экране в тот момент, когда моя программа что-то проверяет.

В этом случае я могу попросить программу немного поспать, а затем перепроверить через секунду или две. Это может означать разницу между прохождением и провалом теста.

Вы можете использоватьdecorator для добавления системного вызова Pythonsleep() в любом из этих случаев. Если вы не знакомы с декораторами или хотите освежить их в памяти, попробуйтеPrimer on Python Decorators. Давайте посмотрим на пример:

import time
import urllib.request
import urllib.error

def sleep(timeout, retry=3):
    def the_real_decorator(function):
        def wrapper(*args, **kwargs):
            retries = 0
            while retries < retry:
                try:
                    value = function(*args, **kwargs)
                    if value is None:
                        return
                except:
                    print(f'Sleeping for {timeout} seconds')
                    time.sleep(timeout)
                    retries += 1
        return wrapper
    return the_real_decorator

sleep() - ваш декоратор. Он принимает значениеtimeout и количество раз, которое должно бытьretry, которое по умолчанию равно 3. Внутриsleep() находится другая функция,the_real_decorator(), которая принимает декорированную функцию.

Наконец, самая внутренняя функцияwrapper() принимает аргументы и аргументы ключевого слова, которые вы передаете декорированной функции. Здесь происходит волшебство! Вы используете циклwhile, чтобы повторить попытку вызова функции. Если есть исключение, вы вызываетеtime.sleep(), увеличиваете счетчикretries и снова пытаетесь запустить функцию.

Теперь перепишитеuptime_bot(), чтобы использовать новый декоратор:

@sleep(3)
def uptime_bot(url):
    try:
        conn = urllib.request.urlopen(url)
    except urllib.error.HTTPError as e:
        # Email admin / log
        print(f'HTTPError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.HTTPError
    except urllib.error.URLError as e:
        # Email admin / log
        print(f'URLError: {e.code} for {url}')
        # Re-raise the exception for the decorator
        raise urllib.error.URLError
    else:
        # Website is up
        print(f'{url} is up')

if __name__ == '__main__':
    url = 'http://www.google.com/py'
    uptime_bot(url)

Здесь вы украшаетеuptime_bot() 3-секунднымsleep(). Вы также удалили исходный циклwhile, а также старый вызовsleep(60). Декоратор теперь позаботится об этом.

Еще одно изменение, которое вы сделали, - это добавлениеraise внутри блоков обработки исключений. Это так, что декоратор будет работать правильно. Вы можете написать декоратор для обработки этих ошибок, но поскольку эти исключения применяются только кurllib, возможно, вам лучше оставить декоратор таким, какой он есть. Таким образом, он будет работать с более широким спектром функций.

Note: Если вы хотите освежить в памяти обработку исключений в Python, посмотритеPython Exceptions: An Introduction.

Есть несколько улучшений, которые вы можете сделать в своем декораторе. Если у него заканчиваются повторные попытки и все равно происходит сбой, вы можете повторно вызвать последнюю ошибку. Декоратор также будет ждать 3 секунды после последнего сбоя, что может и не происходить. Не стесняйтесь попробовать их в качестве упражнения!

Добавление вызова Pythonsleep() с потоками

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

Note: Потоки - это метод выполненияconcurrency в Python. Вы можете запустить несколько потоков одновременно, чтобы увеличить производительность вашего приложения. Если вы не знакомы с потоками в Python, попробуйтеAn Intro to Threading in Python.

Чтобы клиенты не замечали какого-либо замедления, каждый поток должен работать в течение короткого периода времени, а затем находиться в спящем режиме. Есть два способа сделать это:

  1. Используйтеtime.sleep(), как и раньше.

  2. ИспользуйтеEvent.wait() из модуляthreading.

Начнем сtime.sleep().

Используяtime.sleep()

PythonLogging Cookbook показывает хороший пример, который используетtime.sleep(). Модуль Pythonlogging является потокобезопасным, поэтому для этого упражнения он немного полезнее, чем операторыprint(). Следующий код основан на этом примере:

import logging
import threading
import time

def worker(arg):
    while not arg["stop"]:
        logging.debug("worker thread checking in")
        time.sleep(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    info = {"stop": False}
    thread = threading.Thread(target=worker, args=(info,))
    thread_two = threading.Thread(target=worker, args=(info,))
    thread.start()
    thread_two.start()

    while True:
        try:
            logging.debug("Checking in from main thread")
            time.sleep(0.75)
        except KeyboardInterrupt:
            info["stop"] = True
            logging.debug('Stopping')
            break
    thread.join()
    thread_two.join()

if __name__ == "__main__":
    main()

Здесь вы используете модуль Pythonthreading для создания двух потоков. Вы также создаете объект регистрации, который будет регистрироватьthreadName в stdout. Затем вы запускаете оба потока и запускаете цикл для регистрации в основном потоке. Вы используетеKeyboardInterrupt, чтобы поймать пользователя, нажимающегоCtrl+[.kbd .key-c]#C #.

Попробуйте запустить приведенный выше код в своем терминале. Вы должны увидеть вывод, похожий на следующий:

 0 Thread-1 worker thread checking in
 1 Thread-2 worker thread checking in
 1 MainThread Checking in from main thread
752 MainThread Checking in from main thread
1001 Thread-1 worker thread checking in
1001 Thread-2 worker thread checking in
1502 MainThread Checking in from main thread
2003 Thread-1 worker thread checking in
2003 Thread-2 worker thread checking in
2253 MainThread Checking in from main thread
3005 Thread-1 worker thread checking in
3005 MainThread Checking in from main thread
3005 Thread-2 worker thread checking in

Когда каждый поток запускается, а затем спит, выходные данные журнала выводятся на консоль. Теперь, когда вы попробовали пример, вы сможете использовать эти концепции в своем собственном коде.

ИспользуяEvent.wait()

Модульthreading предоставляетEvent(), который вы можете использовать какtime.sleep(). Однако уEvent() есть дополнительное преимущество - он более отзывчивый. Причина этого заключается в том, что когда событие установлено, программа немедленно выходит из цикла. Сtime.sleep() ваш код должен будет дождаться завершения вызова Pythonsleep(), прежде чем поток сможет выйти.

Причина, по которой вы хотите использовать здесьwait(), заключается в том, чтоwait() - этоnon-blocking, аtime.sleep() - этоblocking. Это означает, что когда вы используетеtime.sleep(), вы блокируете продолжение работы основного потока, пока он ожидает завершения вызоваsleep(). wait() решает эту проблему. Вы можете узнать больше о том, как все это работает, вthreading documentation Python.

Вот как добавить вызов Pythonsleep() сEvent.wait():

import logging
import threading

def worker(event):
    while not event.isSet():
        logging.debug("worker thread checking in")
        event.wait(1)

def main():
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(relativeCreated)6d %(threadName)s %(message)s"
    )
    event = threading.Event()

    thread = threading.Thread(target=worker, args=(event,))
    thread_two = threading.Thread(target=worker, args=(event,))
    thread.start()
    thread_two.start()

    while not event.isSet():
        try:
            logging.debug("Checking in from main thread")
            event.wait(0.75)
        except KeyboardInterrupt:
            event.set()
            break

if __name__ == "__main__":
    main()

В этом примере вы создаетеthreading.Event() и передаете егоworker(). (Вспомните, что в предыдущем примере вы вместо этого передали словарь.) Затем вы настраиваете свои циклы, чтобы проверить, установлен лиevent. Если это не так, то ваш код печатает сообщение и немного ждет, прежде чем проверять снова. Чтобы установить событие, вы можете нажать [.keys] #Ctrl [.kbd .key-c] # C ##. Как только событие установлено, `+ worker ()` вернется, и цикл прервется, завершив программу.

Note: Если вы хотите узнать больше о словарях, посмотритеDictionaries in Python.

Присмотритесь к блоку кода выше. Как бы вы передавали разное время сна каждому рабочему потоку? Вы можете понять это? Не стесняйтесь решать это упражнение самостоятельно!

Добавление вызова Pythonsleep() с асинхронным вводом-выводом

Асинхронные возможности были добавлены в Python в версии 3.4, и с тех пор этот набор функций активно расширяется. Asynchronous programming - это тип параллельного программирования, позволяющий запускать несколько задач одновременно. Когда задача заканчивается, она уведомляет основной поток.

asyncio - это модуль, который позволяет вам асинхронно добавлять вызов Pythonsleep(). Если вы не знакомы с реализацией асинхронного программирования в Python, обратите внимание наAsync IO in Python: A Complete Walkthrough иPython Concurrency & Parallel Programming.

Вот пример изdocumentation Python:

import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# Python 3.7+
asyncio.run(main())

В этом примере вы запускаетеmain(), и он находится в спящем режиме на одну секунду между двумя вызовамиprint().

Вот более убедительный пример из разделаCoroutines and Tasks документацииasyncio:

import asyncio
import time

async def output(sleep, text):
    await asyncio.sleep(sleep)
    print(text)

async def main():
    print(f"Started: {time.strftime('%X')}")
    await output(1, 'First')
    await output(2, 'Second')
    await output(3, 'Third')
    print(f"Ended: {time.strftime('%X')}")

# Python 3.7+
asyncio.run(main())

В этом коде вы создаете воркера с именемoutput(), которому требуется количество секунд доsleep иtext для печати. Затем вы используете ключевое слово Pythonawait, чтобы дождаться запуска кодаoutput(). Здесь требуетсяawait, потому чтоoutput() отмечен как функцияasync, поэтому вы не можете вызывать ее, как обычную функцию.

Когда вы запустите этот код, ваша программа выполнитawait 3 раза. Код будет ждать 1, 2 и 3 секунды, а общее время ожидания составит 6 секунд. Вы также можете переписать код так, чтобы задачи выполнялись параллельно:

import asyncio
import time

async def output(text, sleep):
    while sleep > 0:
        await asyncio.sleep(1)
        print(f'{text} counter: {sleep} seconds')
        sleep -= 1

async def main():
    task_1 = asyncio.create_task(output('First', 1))
    task_2 = asyncio.create_task(output('Second', 2))
    task_3 = asyncio.create_task(output('Third', 3))
    print(f"Started: {time.strftime('%X')}")
    await task_1
    await task_2
    await task_3
    print(f"Ended: {time.strftime('%X')}")

if __name__ == '__main__':
    asyncio.run(main())

Теперь вы используете концепциюtasks, которую можно создать с помощьюcreate_task(). Когда вы используете задачи вasyncio, Python будет запускать задачи асинхронно. Таким образом, когда вы запускаете приведенный выше код, он должен завершиться всего за 3 секунды вместо 6.

Добавление вызова Pythonsleep() с графическим интерфейсом пользователя

Приложения командной строки - не единственное место, где вам может потребоваться добавить вызовы Pythonsleep(). Когда вы создаетеGraphical User Interface (GUI), вам иногда нужно добавлять задержки. Например, вы можете создать приложение FTP для загрузки миллионов файлов, но вам нужно добавить вызовsleep() между пакетами, чтобы не перегружать сервер.

Код GUI будет выполнять всю свою обработку и рисование в основном потоке, называемомevent loop. Если вы используетеtime.sleep() внутри кода графического интерфейса пользователя, вы заблокируете его цикл обработки событий. С точки зрения пользователя, приложение может зависнуть. Пользователь не сможет взаимодействовать с вашим приложением, пока оно спит с этим методом. (В Windows вы можете даже получить предупреждение о том, что ваше приложение теперь не отвечает.)

К счастью, помимоtime.sleep() вы можете использовать и другие методы. В следующих нескольких разделах вы узнаете, как добавлять вызовы Pythonsleep() как в Tkinter, так и в wxPython.

Спать в Ткинтере

tkinter является частью стандартной библиотеки Python. Он может быть недоступен, если вы используете предустановленную версию Python для Linux или Mac. Если вы получилиImportError, вам нужно будет подумать, как добавить его в свою систему. Но если выinstall Python yourself, тоtkinter уже должен быть доступен.

Вы начнете с рассмотрения примера, в котором используетсяtime.sleep(). Запустите этот код, чтобы увидеть, что произойдет, если вы неправильно добавите вызов Pythonsleep():

import tkinter
import time

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        b = tkinter.Button(text="click me", command=self.delayed)
        b.pack()

    def delayed(self):
        time.sleep(3)

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

Запустив код, нажмите кнопку в графическом интерфейсе. Кнопка будет удерживаться в течение трех секунд, ожидая завершенияsleep(). Если в приложении есть другие кнопки, вы не сможете их нажимать. Вы также не можете закрыть приложение, когда оно спит, поскольку оно не может ответить на событие закрытия.

Чтобыtkinter нормально засыпал, вам нужно использоватьafter():

import tkinter

class MyApp:
    def __init__(self, parent):
        self.root = parent
        self.root.geometry("400x400")
        self.frame = tkinter.Frame(parent)
        self.frame.pack()
        self.root.after(3000, self.delayed)

    def delayed(self):
        print('I was delayed')

if __name__ == "__main__":
    root = tkinter.Tk()
    app = MyApp(root)
    root.mainloop()

Здесь вы создаете приложение шириной 400 пикселей и высотой 400 пикселей. На нем нет виджетов. Все, что он будет делать, это показать кадр. Затем вы вызываетеself.root.after(), гдеself.root - ссылка на объектTk(). after() принимает два аргумента:

  1. Количество миллисекунд для сна

  2. Метод для вызова, когда сон закончен

В этом случае ваше приложение напечатает строку в стандартный вывод через 3 секунды. Вы можете думать оafter() как о версииtkinter дляtime.sleep(), но она также добавляет возможность вызова функции после завершения сна.

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

Спать в wxPython

Есть два основных различия между wxPython и Tkinter:

  1. У wxPython есть много других виджетов.

  2. wxPython стремится выглядеть и чувствовать себя родным на всех платформах.

Инфраструктура wxPython не входит в состав Python, поэтому вам необходимо установить ее самостоятельно. Если вы не знакомы с wxPython, попробуйтеHow to Build a Python GUI Application With wxPython.

В wxPython вы можете использоватьwx.CallLater() для добавления вызова Pythonsleep():

import wx

class MyFrame(wx.Frame):
    def __init__(self):
        super().__init__(parent=None, title='Hello World')
        wx.CallLater(4000, self.delayed)
        self.Show()

    def delayed(self):
        print('I was delayed')

if __name__ == '__main__':
    app = wx.App()
    frame = MyFrame()
    app.MainLoop()

Здесь вы создаете подклассwx.Frame напрямую, а затем вызываетеwx.CallLater(). Эта функция принимает те же параметры, что иafter() Tkinter:

  1. Количество миллисекунд для сна

  2. Метод для вызова, когда сон закончен

Когда вы запустите этот код, вы увидите маленькое пустое окно без каких-либо виджетов. Через 4 секунды вы увидите строку'I was delayed', выведенную на стандартный вывод.

Одним из преимуществ использованияwx.CallLater() является его многопоточность. Вы можете использовать этот метод из потока, чтобы вызвать функцию из основного приложения wxPython.

Заключение

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

Напомним, вы узнали, как добавлять вызовы Pythonsleep() с помощью следующих инструментов:

  • time.sleep()

  • Декораторы

  • Потоки

  • asyncio

  • Tkinter

  • WxPython

Теперь вы можете взять то, что вы узнали, и начать переводить код в спящий режим!