Друкарня від WE.UA

Клас Timer у модулі threading в Python

Зміст

Всіх вітаю! У цій статті ми розглянемо, як асинхронно запускати код через деякий проміжок часу за допомогою класу Timer вбудованого модуля threading.

Теорія потоків

Суть модуля threading

Модуль threading надає можливість створювати так звані потоки у коді та керувати ними. Потік дозволяє програмі працювати асинхронно, тобто це можливість виконувати багато задач за меншу кількість часу.

Що таке таймери у threading?

Таймери — це різновид потоків, які ми створюємо, щоб виконувати декілька процесів одночасно, виставляючи у них потрібний час виконання того чи іншого завдання.

Це можна легко перевірити у коді:

import threading

timer_is_thread = issubclass(threading.Timer, threading.Thread)
print(timer_is_thread)

Результат:

True

Тут ми порівнюємо клас Timer з класом Thread, який характеризує потоки. Згідно з результатом, Timer є підкласом Thread, а отже, є й потоком.

Приклад звичайної програми без потоків і з ними

Наведемо приклад. Припустимо, у нас є програма, яка отримує дані входу від користувача (ім’я користувача та пароль), перевіряє те, наскільки правильно він ввів дані, а потім перевіряє, чи є такі дані вже у базі даних.

Стандартний код працює поступово: спочатку дає користувачу ввести дані, потім перевіряє правильність введення усіх даних і тільки після цього перевіряє, чи є усі ці дані у базі даних. Весь процес введення та аналізу даних є послідовним.

Тож нашу програму по суті можна розбити на окремі маленькі процеси:

  1. Введення даних.

  2. Перевірка правильності введення даних.

  3. Перевірка інформації по базі даних.

Суть потоків якраз передбачає те, що ми створимо три потоки під кожний процес у програмі. Таким чином, наша програма зможе з легкістю поєднувати виконання декількох процесів одночасно (хоча технічно код все одно не виконується одночасно, просто нам так здається).

Код з використанням потоків здатний виконати усі ці процеси, не чекаючи, поки один із них повністю завершить свою роботу і дасть іншому почати. Цей код просто використає створені потоки для виконання одразу декількох процесів одночасно. Тоді процес перевірки по базі даних не буде чекати, поки процес перевірки правильності введення повністю перевірить усі дані користувача.

У такому разі після перевірки правильності введення імені користувача програма одразу почне перевіряти, чи немає цього імені у базі даних, допоки користувач все ще не ввів пароль.

Переваги використання потоків у коді

Головною перевагою використання потоків є швидкість виконання коду, адже потоки дозволяють витрачати значно менше часу на виконання тих чи інших задач завдяки одночасному виконанню декількох задач.

Створення таймерів

Таймер, що починає виконання функції через визначений проміжок часу

Отже, спочатку нам потрібно імпортувати модуль threading (він вже є у вас, а тому встановлювати його потреби немає):

import threading

Створюємо функцію login():

def login():
    username = input("Username: ")
    password = input("Password: ")

    if username == "William" and password == "qwerty123":
        print("Welcome!")

Створюємо таймер, у якому вказуємо, що функція login() почне виконуватися тільки через 5 секунд (спочатку передаємо інтервал часу, а потім саму функцію):

timer = threading.Timer(5.0, login)

Після цього обов’язково треба запустити таймер:

timer.start()

Ось повний код:

import threading


def login():
    first_name = input("Name: ")
    username = input("Username: ")
    password = input("Password: ")

    if first_name == "William" and username == "@w1lly22" and password == "qwerty123":
        print("Welcome!")


timer = threading.Timer(5.0, login)
timer.start()

Тепер якщо ви запустите цей код, то функція login() і справді запуститься тільки через 5 секунд.

Перший спосіб створення повторювального таймера

Але чи можна зробити так, щоб таймер змушував функцію виконуватися через кожний інтервал часу? Так, але при розгляді цього питання ми зіштовхуємося з однією особливістю потоків у Python.

Ця особливість полягає у тому, що потік можна запустити лише один раз, а тому при використанні циклу ми отримаємо помилку.

Наприклад, спробуємо цей код:

import threading


def no_user_found(first_names: list, usernames: list):
    for first_name, username in zip(first_names, usernames):
        print(f"First name: {first_name}, username: {username}")

        if first_name == "William" and username == "@w1lly22":
            print("The user with these data exits.")

        else:
            print("No user found.")


timer = threading.Timer(5.0, no_user_found)

while True:
    timer.start()

Результат:

RuntimeError: threads can only be started once

Для того, щоб зробити таймер повторювальним, нам варто прислухатися до суті помилки й запустити потік лише один раз. Проте нам потрібно буде створити власний окремий клас, який буде наслідувати об’єкт потоку Thread.

Для вирішення цієї проблеми спочатку нам треба створити власний клас:

class RepeatedTimer(threading.Thread):
    def __init__(self, event):
        threading.Thread.__init__(self)
        self.stopped = event

Тут важливо знати дві речі:

  1. Клас RepeatedTimer наслідує клас потоку Thread.

  2. Поле self.stopped дорівнює змінній event, яка приймає значення True або False і характеризує подію виконання та зупинки таймера RepeatedTimer.

Далі нам потрібно створити метод, який би повторював запуски нашої функції:

def run(self):
    while not self.stopped.wait(5.0):
        no_user_found(first_names=["Alice", "Peter", "Brian", "William"],
                      usernames=["@aliceee", "@petermorton", "@brianllars", "@w1lly22"])

Тут і знаходиться головна логіка таймера. Як ми знаємо, цикл while виконується до тих пір, поки умова є істинною. Тож при запуску програми атрибут event і, відповідно, поле self.stopped будуть дорівнювати False, а not False дорівнює True.

Наступним кроком запускаємо створений таймер:

stop_flag = threading.Event()
custom_thread = RepeatedTimer(stop_flag)
custom_thread.start()

Тут оголошуємо об’єкт stop_flag, який якраз таки і буде тим атрибутом event, який приймає наш клас RepeatedTimer.

Після 10-ти секунд ми змінюємо значення об’єкта stop_flag на True, а так як not True дорівнює False, то цикл while одразу зупиниться.

stop_flag = threading.Event()
custom_thread = RepeatedTimer(stop_flag)
custom_thread.start()

time.sleep(10)
stop_flag.set()

Цей код буде працювати рівно 10 секунд, а потім наш об’єкт stop_flag зупинить програму.

Повний код першого способу

Ось повний код першого способу створення повторювального таймера:

import threading
import time


class RepeatedTimer(threading.Thread):
    def __init__(self, event):
        threading.Thread.__init__(self)
        self.stopped = event

    def run(self):
        while not self.stopped.wait(5.0):
            no_user_found(first_names=["Alice", "Peter", "Brian", "William"],
                          usernames=["@aliceee", "@petermorton", "@brianllars", "@w1lly22"])


stop_flag = threading.Event()
custom_thread = RepeatedTimer(stop_flag)
custom_thread.start()

time.sleep(10)
stop_flag.set()

Другий спосіб створення повторювального таймера

Тим не менш, є й інший спосіб створити повторювальний таймер. Він відрізняється тим, що замість класу потоку Thread наш клас буде наслідувати конкретно клас Timer.

Ось наша функція, створена раніше

def no_user_found(first_names: list, usernames: list):
    for first_name, username in zip(first_names, usernames):
        print(f"First name: {first_name}, username: {username}")

        if first_name == "William" and username == "@w1lly22":
            print("The user with these data exits.")

        else:
            print("No user found.")

Ось код другого варіанту створення повторювального таймера:

class RepeatedTimer(threading.Timer):
    def run(self):
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)

Тут логіка роботи така сама, як і у попередньому варіанті, бо self.finished, який стоїть в умові, є об’єктом класу Event(), який ми використовували у минулому прикладі.

Змінна self.interval — це інтервал часу, через який наша функція self.function буде повторюватися.

Тоді все стає зрозумілішим, бо спочатку умова у циклі while дорівнює not False, тобто True. Тому поки умова буде істинною, функція буде повторюватися знову і знову.

Тілом циклу у даному випадку є рядок self.function(*args, **kwargs), який характеризує функцію, яку ми будемо передавати у клас RepeatedTimer.

Тим часом *args та **kwargs — це спеціальні параметри, які допомагають адаптувати цей код до будь-яких функцій, які ми тільки можемо передати у RepeatedTimer, адже ніхто не може передбачити, яку функцію та зі скількома параметрами умовний розробник захоче передати.

Саме завдяки зірочкам (*) та (**) наш повторювальний таймер буде працювати незалежно від кількості параметрів, які ми задамо для функції no_user_found().

Якщо ви не стикалися з параметрами *args та **kwargs до цього, то раджу почитати про це у розділі нижче.

Далі створюємо об’єкт timer, використовуючи наш клас RepeatedTimer, куди ми спочатку передаємо інтервал часу для повторення функції та саму функцію no_user_found.

Варто зауважити, що другий варіант реалізації повторювального таймера виключає запис параметрів у функцію тим чином, яким ми це робили раніше, а саме:

no_user_found(first_names=["Alice", "Peter", "Brian", "William"],
              usernames=["@aliceee", "@petermorton", "@brianllars", "@w1lly22"])

Замість цього нам потрібно буде вказати значення параметрів у вигляді словника та передати параметру kwargs, який і відповідає за роботу зі словниками:

timer = RepeatedTimer(1, no_user_found, kwargs={
    "first_names": ["Alice", "Peter", "Brian", "William"],
    "usernames": ["@aliceee", "@petermorton", "@brianllars", "@w1lly22"]
    })

timer.start()

Тепер коротко розберемо, що ми передали у клас RepeatedTimer():

  • Спершу передаємо параметр interval.

  • Далі передаємо саму функцію no_user_found.

  • Словник із списками.

Зупиняємо таймер (і виконання функції, відповідно) через 10 секунд:

time.sleep(10)
timer.cancel()

Також треба розуміти, що наша функція буде повторюватися саме протягом цих 10 секунд, допоки ми не скасуємо таймер.

Повний код другого способу

Ось повний код другого способу створення повторювального таймера:

import threading
import time


class RepeatedTimer(threading.Timer):
    def run(self):
        while not self.finished.wait(self.interval):
            self.function(*self.args, **self.kwargs)


def no_user_found(first_names: list, usernames: list):
    for first_name, username in zip(first_names, usernames):
        print(f"First name: {first_name}, username: {username}")

        if first_name == "William" and username == "@w1lly22":
            print("The user with these data exits.")

        else:
            print("No user found.")


timer = RepeatedTimer(1, no_user_found, kwargs={
    "first_names": ["Alice", "Peter", "Brian", "William"],
    "usernames": ["@aliceee", "@petermorton", "@brianllars", "@w1lly22"]
    })

timer.start()

time.sleep(10)
timer.cancel()

Спеціальні параметри *args та **kwargs

У Python позначення *args — це позиційні аргументи, тобто списки, множини і т. д.

Тут варто розуміти, що важливі тут лише позначення зірочок, а не самі записи args чи kwargs. Саме записи (*) або (**) і відповідають за правильне розкладання елементів представлених у цьому підрозділі типів даних.

Коли ми передаємо такі типи даних у функцію як параметри, то одна зірочка (*) дістає усі елементи у цих типах даних і надає їх функції у вигляді звичайних значень, що записані через кому.

Ось приклад:

user_info = ["John", "Peterson", "@johnnypi", "admin"]


def account_private_info(first_name, last_name, username, password):
    print(f"USER INFO\n"
          f"============================\n"
          f"First name:   {first_name}\n"
          f"Last name:    {last_name}\n"
          f"Username:     {username}\n"
          f"Password:     {password}")


account_private_info(*user_info)

Результат:

USER INFO
============================
First name:   John
Last name:    Peterson
Username:     @johnnypi
Password:     admin

Як бачимо, за допомогою однієї зірочки ми змогли легко розбити усі елементи списку на окремі параметри, які вказали при оголошенні функції.

Цей код за результатом рівноцінний отакому запису:

user_info = ["John", "Peterson", "@johnnypi", "admin"]


def account_private_info(first_name, last_name, username, password):
    print(f"USER INFO\n"
          f"============================\n"
          f"First name:   {first_name}\n"
          f"Last name:    {last_name}\n"
          f"Username:     {username}\n"
          f"Password:     {password}")


account_private_info(user_info[0], user_info[1], user_info[2], user_info[3])

Просто перший запис є набагато зручнішим завдяки чіткому розбиттю елементів списку. Тут же нам довелося записати значення усіх параметрів вручну при виклику функції.

Позначення **kwargs означає іменовані аргументи, тобто словники. В іншому дві зірочки (**) роблять усе те саме, що й одна, але вони працюють з різними типами даних.

Приклад:

user_info = {"first_name": "John",
             "last_name": "Peterson",
             "username": "@johnnypi",
             "password": "admin"}


def account_private_info(first_name, last_name, username, password):
    print(f"USER INFO\n"
          f"============================\n"
          f"First name:   {first_name}\n"
          f"Last name:    {last_name}\n"
          f"Username:     {username}\n"
          f"Password:     {password}")


account_private_info(**user_info)

Цей код рівноцінний такому запису:

user_info = {"first_name": "John",
             "last_name": "Peterson",
             "username": "@johnnypi",
             "password": "admin"}


def account_private_info(first_name, last_name, username, password):
    print(f"USER INFO\n"
          f"============================\n"
          f"First name:   {first_name}\n"
          f"Last name:    {last_name}\n"
          f"Username:     {username}\n"
          f"Password:     {password}")


account_private_info(user_info["first_name"], user_info["last_name"], user_info["username"], user_info["password"])

Знову-таки, перший варіант з використанням двох зірочок є кращим.

Висновок

У цій статті ми ознайомилися з модулем threading та класом Timer. Також ми детально розібрали два варіанти того, як зробити повторювальний таймер.

Список джерел
  1. Код варіантів створення повторювального таймера

Статті про вітчизняний бізнес та цікавих людей:

Поділись своїми ідеями в новій публікації.
Ми чекаємо саме на твій довгочит!
Magnifique numérique
Magnifique numérique@magnifiquedigitalworld

Прекрасний цифровий світ

96Довгочити
2KПерегляди
28Підписники
Підтримати
На Друкарні з 14 липня 2025

Більше від автора

  • Відтепер для застосунків на Base44 можна додавати push-повідомлення

    Нещодавно компанія-розробник платформи Base44, яка дозволяє створювати застосунки за допомогою ШІ, заявила, що відтепер до своїх застосунків можна підключати push-повідомлення.

    Теми цього довгочиту:

    It Новини
  • Оновлення проєкту Magnifique numérique

    Відтепер Magnifique numérique — це не лише про статті з програмування, а й новини про IT та технології. Це означає, що відтепер контент стане різноманітнішим.

    Теми цього довгочиту:

    Оновлення
  • Метод zfill() у Python

    У цій статті ми розберемо метод zfill() у Python на основі практичних прикладів його використання.

    Теми цього довгочиту:

    It

Це також може зацікавити:

  • Як ноутбук може стати найкращим другом вашої дитини (і чому це ок)

    Погляньмо правді в очі: у наш час ноутбук — це вже не просто гаджет. Для сучасних дітей він може стати вікном у світ можливостей, інструментом для розвитку і навіть місцем для пошуку друзів. І якщо це звучить тривожно, не поспішайте хвилюватися. Зараз розповімо, чому

    Теми цього довгочиту:

    Батьківство
  • Java. ReadWriteLock

    Для цього використовується ThreadLocalHoldCounter, яка відображає потоки на їхні..

    Теми цього довгочиту:

    Java
  • Як захистити WiFi від хакерів

    Зламати Wi-Fi набагато простіше, ніж здається. Але є хороша новина: дотримуючись кількох простих кроків, навіть звичайний користувач може захистити свій домашній роутер від п’яти найпоширеніших методів злому.

    Теми цього довгочиту:

    Кібербезпека

Коментарі (0)

Підтримайте автора першим.
Напишіть коментар!

Це також може зацікавити:

  • Як ноутбук може стати найкращим другом вашої дитини (і чому це ок)

    Погляньмо правді в очі: у наш час ноутбук — це вже не просто гаджет. Для сучасних дітей він може стати вікном у світ можливостей, інструментом для розвитку і навіть місцем для пошуку друзів. І якщо це звучить тривожно, не поспішайте хвилюватися. Зараз розповімо, чому

    Теми цього довгочиту:

    Батьківство
  • Java. ReadWriteLock

    Для цього використовується ThreadLocalHoldCounter, яка відображає потоки на їхні..

    Теми цього довгочиту:

    Java
  • Як захистити WiFi від хакерів

    Зламати Wi-Fi набагато простіше, ніж здається. Але є хороша новина: дотримуючись кількох простих кроків, навіть звичайний користувач може захистити свій домашній роутер від п’яти найпоширеніших методів злому.

    Теми цього довгочиту:

    Кібербезпека