uz
Feedback
Python для начинающих

Python для начинающих

Kanalga Telegram’da o‘tish

Python для начинающих

Ko'proq ko'rsatish
1 240
Obunachilar
Ma'lumot yo'q24 soatlar
+17 kunlar
+130 kunlar
Postlar arxiv
Работа с кешированием данных: file-based и memory-based подходы ================================================================ Кеширование — это способ «не думать дважды». Если результат уже посчитан, зачем снова тратить время и ресурсы? В Python это особенно актуально при работе с медленными запросами к API, базам данных или тяжёлым вычислениям. Разберём два простых подхода: кеш в памяти и кеш в файле. ## Memory-based: быстро, но временно Кеш в памяти живёт только пока работает программа. Зато он очень быстрый.
import time
from functools import lru_cache

@lru_cache(maxsize=128)
def slow_square(n: int) -> int:
    time.sleep(1)  # имитация долгой операции
    return n * n

if __name__ == "__main__":
    start = time.time()
    print(slow_square(10))  # первый вызов — медленно
    print("First call:", time.time() - start)

    start = time.time()
    print(slow_square(10))  # второй вызов — из кеша
    print("Second call:", time.time() - start)
lru_cache: - автоматически хранит результаты по аргументам; - ограничивает размер кеша (maxsize); - идеально подходит для функций без побочных эффектов (чистые функции). Минус: как только программа завершилась — кеш исчез. ## File-based: медленнее, но переживает перезапуск Кеш в файле сохраняет результаты между запусками. Удобно для скриптов, которые периодически запускаются по расписанию. Самый простой способ — использовать pickle или json. Пример с json:
import json
import os
import time
from typing import Any, Dict

CACHE_FILE = "cache.json"

def load_cache() -> Dict[str, Any]:
    if not os.path.exists(CACHE_FILE):
        return {}
    with open(CACHE_FILE, "r", encoding="utf-8") as f:
        return json.load(f)

def save_cache(cache: Dict[str, Any]) -> None:
    with open(CACHE_FILE, "w", encoding="utf-8") as f:
        json.dump(cache, f)

def get_data(param: str) -> str:
    time.sleep(1)  # имитация тяжёлого запроса
    return f"result_for_{param}"

def cached_get_data(param: str) -> str:
    cache = load_cache()
    if param in cache:
        return cache[param]

    result = get_data(param)
    cache[param] = result
    save_cache(cache)
    return result

if __name__ == "__main__":
    print(cached_get_data("foo"))  # первый раз — долго
    print(cached_get_data("foo"))  # второй раз — сразу
Плюсы: - данные сохраняются между запусками; - можно легко чистить/анализировать кеш-файл. Минусы: - медленнее RAM; - нужно следить за размером файла и «протуханием» данных. ## Что выбрать? - Нужна скорость внутри одного процесса — берите lru_cache. - Важно экономить время между запусками — храните кеш в файле. - В реальных проектах их часто комбинируют: быстрый memory-кеш поверх более «упорного» file-кеша. Кеширование — один из тех инструментов, которые мгновенно делают ваш код ощущаемо «умнее» и быстрее, если применять его осознанно.

Работа с кешированием данных: file-based и memory-based подходы
Работа с кешированием данных: file-based и memory-based подходы

Как использовать watchdog для отслеживания изменений в файлах Иногда хочется, чтобы скрипт сам реагировал на изменения в файлах: перезапускал сборку, обновлял данные, копировал новый файл в бэкап. Переписывать бесконечные циклы while True с time.sleep — скучно и неэффективно. Для этого есть отличный модуль — watchdog. --- ### Установка
pip install watchdog
watchdog работает кроссплатформенно и умеет «слушать» файловую систему почти как системные утилиты. --- ### Простейший пример: реагируем на изменения в папке Создадим наблюдателя, который будет печатать, когда что-то происходит в директории:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import time
from pathlib import Path


class SimpleHandler(FileSystemEventHandler):
    def on_modified(self, event):
        print(f"Modified: {event.src_path}")

    def on_created(self, event):
        print(f"Created: {event.src_path}")

    def on_deleted(self, event):
        print(f"Deleted: {event.src_path}")


if __name__ == "__main__":
    path = Path(".").resolve()
    event_handler = SimpleHandler()
    observer = Observer()
    observer.schedule(event_handler, str(path), recursive=True)
    observer.start()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()
Теперь, если в папке создать/изменить/удалить файл — скрипт отреагирует. --- ### Фильтрация по типу файлов Часто нужно отслеживать только, скажем, .py или .txt. Добавим простую проверку:
class PyFilesHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.is_directory:
            return
        if not event.src_path.endswith(".py"):
            return
        print(f"Python file changed: {event.src_path}")
Так можно запускать, например, линтер или тесты только при изменении исходников. --- ### Автоматическая реакция: мини‑автосборка Пример: при изменении файла конфигурации перегенерировать выходной файл.
import subprocess


class BuildHandler(FileSystemEventHandler):
    def on_modified(self, event):
        if event.is_directory:
            return
        if event.src_path.endswith("config.yaml"):
            print("Config changed, rebuilding...")
            subprocess.run(["python", "build.py"])
Теперь любое сохранение config.yaml автоматически запускает build.py. --- watchdog — мощный инструмент для автоматизации рутинных задач вокруг файловой системы: автосборка проектов, синхронизация папок, логирование изменений. Главное — один раз написать обработчик событий, а дальше файлы будут «говорить» с вашим кодом сами.

Как использовать watchdog для отслеживания изменений в файлах
Как использовать watchdog для отслеживания изменений в файлах

Модуль statistics: среднее, медиана и дисперсия без боли в голове Когда данные растут, как снежный ком, возникает вопрос: «И что со всем этим делать?» В Python для этого есть модуль statistics — маленький, но очень полезный набор инструментов для анализа чисел. --- ### Среднее: mean Среднее арифметическое — это классическое "в среднем по больнице".
from statistics import mean

temperatures = [18, 20, 21, 19, 22, 20]
avg_temp = mean(temperatures)
print(avg_temp)  # 20.0
mean() чувствителен к выбросам. Если добавить одно странное значение, картина исказится:
from statistics import mean

salaries = [50_000, 55_000, 52_000, 60_000, 1_000_000]
print(mean(salaries))  # 243000.0 — выглядит уже не так реалистично
--- ### Медиана: median и median_low / median_high Медиана — "середина" отсортированных данных. Она куда устойчивее к выбросам.
from statistics import median

salaries = [50_000, 55_000, 52_000, 60_000, 1_000_000]
print(median(salaries))  # 55_000 — намного ближе к реальности
Для четного количества значений можно управлять поведением:
from statistics import median_low, median_high

values = [1, 2, 100, 101]
print(median_low(values))   # 2
print(median_high(values))  # 100
--- ### Дисперсия и стандартное отклонение: насколько данные "разбросаны" Если среднее говорит "где центр", то дисперсия и стандартное отклонение — "насколько всё вокруг центра разлетается".
from statistics import variance, stdev

scores = [70, 72, 71, 69, 70, 71]
print(variance(scores))  # маленькое значение — все близко к среднему
print(stdev(scores))     # стандартное отклонение
Чем больше дисперсия и стандартное отклонение, тем сильнее отличаются значения внутри набора. Важно: variance() и stdev() требуют минимум 2 значения. Для всей генеральной совокупности есть pvariance() и pstdev() — используют немного другие формулы:
from statistics import pvariance, pstdev

scores = [70, 72, 71, 69, 70, 71]
print(pvariance(scores))
print(pstdev(scores))
--- ### Небольшой практический пример Оценим стабильность времени отклика сервиса:
from statistics import mean, median, stdev

response_times = [110, 120, 115, 300, 118, 117, 119]

print("mean:", mean(response_times))
print("median:", median(response_times))
print("stdev:", stdev(response_times))
Если среднее сильно больше медианы и стандартное отклонение велико — у вас есть редкие, но тяжелые "подвисания", которые нельзя игнорировать. --- statistics — идеальный вход в мир анализа данных: минимум кода, максимум информации о ваших числах. Начните с этих функций, и набор цифр перестанет быть просто хаосом.

Модуль statistics: работа со средним, медианой и дисперсией
Модуль statistics: работа со средним, медианой и дисперсией

Модуль statistics: работа со средним, медианой и дисперсией
Модуль statistics: работа со средним, медианой и дисперсией

Изучение шаблонов проектирования на простых примерах: Singleton, Factory В какой‑то момент любой код на Python вырастает до такого размера, что простых функций и классов уже мало. Хочется порядка. Тут в игру выходят шаблоны проектирования. Разберём два базовых — Singleton и Factory — на минимальных, но жизненных примерах. --- ## Singleton: когда нужен только один экземпляр Singleton гарантирует, что у класса будет всего один объект. Полезно для настроек приложения, логгера, подключения к БД. ### Пример: глобальные настройки приложения
class AppConfig:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
            cls._instance._initialized = False
        return cls._instance

    def __init__(self, debug=False):
        if self._initialized:
            return
        self.debug = debug
        self._initialized = True


cfg1 = AppConfig(debug=True)
cfg2 = AppConfig(debug=False)

print(cfg1 is cfg2)       # True
print(cfg1.debug, cfg2.debug)  # True True
__new__ контролирует создание объекта и хранит единственный экземпляр в _instance. __init__ запускается при каждом вызове класса, поэтому мы защищаемся флагом _initialized, чтобы не перезаписывать настройки. --- ## Factory: когда нужно создавать разные объекты одинаковым способом Factory инкапсулирует логику создания объектов. Внешнему коду не важно, какой именно класс используется — он просто просит «сделай мне подходящий объект». ### Пример: парсер данных в разных форматах
class JsonParser:
    def parse(self, data: str):
        return {"type": "json", "raw": data}


class XmlParser:
    def parse(self, data: str):
        return {"type": "xml", "raw": data}


class ParserFactory:
    @staticmethod
    def get_parser(format_name: str):
        format_name = format_name.lower()
        if format_name == "json":
            return JsonParser()
        if format_name == "xml":
            return XmlParser()
        raise ValueError(f"Unknown format: {format_name}")


parser = ParserFactory.get_parser("json")
result = parser.parse('{"id": 1}')
print(result)
Теперь, если появится YamlParser, мы просто добавим его в ParserFactory, не переписывая весь остальной код. --- Singleton помогает централизовать состояние, а Factory — спрятать логику создания объектов за единым интерфейсом. Оба шаблона отлично тренируют умение думать о структуре программы, а не только о том, «как бы это запустить, чтобы не упало».

Изучение шаблонов проектирования на простых примерах: Singleton, Factory
Изучение шаблонов проектирования на простых примерах: Singleton, Factory

Как написать простой чат-сервер с использованием asyncio Асинхронность в Python звучит пугающе, но именно она позволяет одному скрипту одновременно обслуживать десятки клиентов. Отличный способ это почувствовать — написать свой мини-чат-сервер на asyncio. ### Идея чата У нас будет: - сервер, который принимает подключения; - список активных клиентов; - функция, рассылающая сообщения всем подключённым. Используем высокоуровневый API: asyncio.start_server, StreamReader и StreamWriter. ### Базовый сервер
import asyncio

clients = set()

async def handle_client(reader: asyncio.StreamReader,
                        writer: asyncio.StreamWriter):
    addr = writer.get_extra_info("peername")
    clients.add(writer)
    print(f"Client connected: {addr}")

    try:
        while data := await reader.readline():
            message = data.decode().rstrip()
            if not message:
                continue
            full_msg = f"{addr}: {message}\n"
            await broadcast(full_msg, sender=writer)
    except asyncio.IncompleteReadError:
        pass
    finally:
        print(f"Client disconnected: {addr}")
        clients.remove(writer)
        writer.close()
        await writer.wait_closed()

async def broadcast(message: str, sender: asyncio.StreamWriter | None = None):
    dead = []
    for client in clients:
        if client is sender:
            continue
        try:
            client.write(message.encode())
            await client.drain()
        except ConnectionError:
            dead.add(client)
    for client in dead:
        clients.discard(client)

async def main():
    server = await asyncio.start_server(handle_client, "127.0.0.1", 8888)
    addr = ", ".join(str(sock.getsockname()) for sock in server.sockets)
    print(f"Server started at {addr}")
    async with server:
        await server.serve_forever()

if __name__ == "__main__":
    asyncio.run(main())
### Как это запустить и протестировать 1. Запускаете сервер: python chat_server.py. 2. В другом терминале подключаетесь через telnet или nc: - telnet 127.0.0.1 8888 или - nc 127.0.0.1 8888 3. Откройте 2–3 таких клиента и начните печатать сообщения. Всё, что отправляет один клиент, увидят остальные. ### Что важно понять - async def и await позволяют не блокировать сервер во время ожидания ввода. - start_server создаёт корутину, которая для каждого клиента запускает handle_client. - broadcast показывает ключевую идею: один сервер — много клиентов, минимум блокировок. Дальше можно добавить ники, команды (/quit, /users), простую авторизацию — всё это поверх уже готового асинхронного фундамента.

Как написать простой чат-сервер с использованием asyncio
Как написать простой чат-сервер с использованием asyncio

Python для начинающих: структурированные логи без боли и магии Обычный print() в продакшене — как дневник на стикерах: пока записей мало, всё понятно, но стоит коду вырасти — и начинается хаос. Здесь на сцену выходит модуль logging и особенно — форматирование логов. --- ### Простейший логгер с форматом Модуль logging уже в стандартной библиотеке. Начнем с базовой настройки:
import logging

logger = logging.getLogger("app")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()

formatter = logging.Formatter(
    fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("Application started")
logger.warning("Low disk space")
Теперь вместо разрозненных сообщений у вас есть чёткая структура: время, уровень, имя логгера, текст. --- ### Логи, которые можно парсить: JSON-формат Когда логов много, их хочется складывать в системы вроде ELK или Loki. Для этого удобно писать их в JSON. Минимальный пример без сторонних библиотек:
import logging
import json
import sys
from datetime import datetime

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_record = {
            "time": datetime.fromtimestamp(record.created).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        if record.exc_info:
            log_record["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_record, ensure_ascii=False)

logger = logging.getLogger("json_app")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(JsonFormatter())
logger.addHandler(handler)

logger.info("User logged in", extra={"user_id": 42})
Обрати внимание: extra по умолчанию не попадёт в log_record. Чтобы добавлять свои поля, можно расширить JsonFormatter и пройтись по record.__dict__, отфильтровав служебные ключи. --- ### Контекст: больше данных, меньше шума Ещё одна фишка — LoggerAdapter. Он позволяет приклеить к логам контекст (например, request_id):
import logging

base_logger = logging.getLogger("service")
base_logger.setLevel(logging.INFO)
base_handler = logging.StreamHandler()
base_handler.setFormatter(logging.Formatter("%(levelname)s | %(request_id)s | %(message)s"))
base_logger.addHandler(base_handler)

class RequestAdapter(logging.LoggerAdapter):
    def process(self, msg, kwargs):
        kwargs.setdefault("extra", {})
        kwargs["extra"].setdefault("request_id", self.extra.get("request_id", "-"))
        return msg, kwargs

logger = RequestAdapter(base_logger, {"request_id": "abc-123"})

logger.info("Processing started")
Теперь все логи в рамках запроса будут помечены одним request_id — дебагить станет гораздо проще. --- Структурированные логи — это не «красивее вывод», а фундамент для нормальной отладки и мониторинга. Настроив форматы однажды, вы перестанете бояться смотреть на логи крупных проектов.

Работа со структурированными логами: использование логгеров с форматами
Работа со структурированными логами: использование логгеров с форматами

Как использовать модуль subprocess для запуска внешних команд Иногда Python‑кода мало. Нужно дернуть системную утилиту: запустить ping, запустить другой скрипт, конвертировать видео через ffmpeg. Для этого есть модуль subprocess — безопасная и гибкая альтернатива старым os.system. --- ### Простой запуск и получение вывода Самый удобный вход — функция subprocess.run. Она запускает команду, ждет завершения и возвращает объект с результатом:
import subprocess

result = subprocess.run(
    ["echo", "Hello from subprocess"],
    capture_output=True,
    text=True
)

print("Return code:", result.returncode)
print("Stdout:", result.stdout)
print("Stderr:", result.stderr)
Ключевые моменты: - Команда передаётся как список: ["echo", "text"], а не одной строкой. Это безопаснее и избавляет от проблем с кавычками. - capture_output=True собирает stdout и stderr. - text=True декодирует байты в строки (по умолчанию UTF-8). --- ### Обработка ошибок Если вы хотите, чтобы Python падал при ошибке команды, используйте check=True:
import subprocess

try:
    subprocess.run(
        ["ls", "/definitely_not_exists"],
        check=True,
        capture_output=True,
        text=True
    )
except subprocess.CalledProcessError as exc:
    print("Command failed!")
    print("Return code:", exc.returncode)
    print("Stderr:", exc.stderr)
CalledProcessError содержит всю полезную информацию о провалившейся команде. --- ### Передача данных на stdin Иногда внешней программе нужно что‑то «скормить» через стандартный ввод:
import subprocess

text_to_send = "line 1\nline 2\n"

result = subprocess.run(
    ["grep", "line"],
    input=text_to_send,
    text=True,
    capture_output=True
)

print(result.stdout)
Параметр input= передает данные в stdin процесса. --- ### Непосредственное взаимодействие: Popen Если нужно более тонкое управление (долгоживущий процесс, чтение вывода построчно и т.п.), используйте Popen:
import subprocess

proc = subprocess.Popen(
    ["ping", "-c", "3", "python.org"],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

for line in proc.stdout:
    print("PING OUTPUT:", line.strip())

return_code = proc.wait()
print("Return code:", return_code)
Здесь процесс живет «параллельно», а вы можете читать его вывод по мере поступления. --- ### Кратко по безопасности - Не передавайте в subprocess строки, собранные из непроверенного пользовательского ввода. - Предпочитайте список аргументов вместо одной строки. - Не включайте shell=True, если это не абсолютно необходимо. Именно shell=True чаще всего открывает дверь инъекциям команд. --- subprocess — это мост между вашим Python‑кодом и всем остальным миром командной строки. Освоив его, вы сможете автоматизировать практически любую системную задачу, не покидая Python.

Как использовать модуль subprocess для запуска внешних команд
Как использовать модуль subprocess для запуска внешних команд

### Работа с zip-архивами с помощью стандартного zipfile Zip-архивы — это такой «контейнер» для файлов: удобно отправить, сохранить, логировать бэкапы. В Python для этого ничего устанавливать не нужно — в стандартной библиотеке уже есть модуль zipfile. --- ## Создаем zip-архив Начнем с базового: упакуем несколько файлов в один архив.
import zipfile
from pathlib import Path

def create_archive(archive_name, files):
    with zipfile.ZipFile(archive_name, mode="w", compression=zipfile.ZIP_DEFLATED) as zf:
        for file_path in files:
            file_path = Path(file_path)
            zf.write(file_path, arcname=file_path.name)

create_archive("project.zip", ["main.py", "config.json", "README.md"])
Ключевые моменты: - mode="w" — создать новый архив (старый с таким именем будет перезаписан). - compression=ZIP_DEFLATED — сжатие, без него файлы просто «складываются» в контейнер. - arcname — имя файла внутри архива (можно отличать от исходного пути). --- ## Добавляем файлы в существующий архив Иногда нужно дописать архив, не пересоздавая его:
import zipfile

def append_to_archive(archive_name, file_path):
    with zipfile.ZipFile(archive_name, mode="a", compression=zipfile.ZIP_DEFLATED) as zf:
        zf.write(file_path)

append_to_archive("project.zip", "log.txt")
Режим a — «append», дозапись. --- ## Просмотр содержимого архива Посмотрим, что внутри, без распаковки:
import zipfile

def list_archive(archive_name):
    with zipfile.ZipFile(archive_name, mode="r") as zf:
        for info in zf.infolist():
            print(f"{info.filename:20} {info.file_size:8} bytes")

list_archive("project.zip")
Объект ZipInfo содержит размер, дату изменения, атрибуты и т.д. --- ## Распаковка архива Распакуем либо всё, либо один конкретный файл:
import zipfile

def extract_all(archive_name, target_dir):
    with zipfile.ZipFile(archive_name, mode="r") as zf:
        zf.extractall(path=target_dir)

def extract_file(archive_name, member, target_dir):
    with zipfile.ZipFile(archive_name, mode="r") as zf:
        zf.extract(member, path=target_dir)

extract_all("project.zip", "unpacked/")
extract_file("project.zip", "config.json", "configs/")
--- ## Чтение файла прямо из архива Иногда распаковывать на диск не хочется — удобнее прочитать файл «на лету»:
import zipfile

def read_text_from_zip(archive_name, member):
    with zipfile.ZipFile(archive_name, mode="r") as zf:
        with zf.open(member) as f:
            return f.read().decode("utf-8")

content = read_text_from_zip("project.zip", "README.md")
print(content)
Так можно, например, читать настройки, ресурсы или шаблоны прямо из архива. --- zipfile покрывает 90% бытовых задач с архивами: упаковка, дописывание, просмотр, извлечение и чтение «на лету». Всё это — без сторонних библиотек и с парой строк кода.

Работа с zip-архивами с помощью стандартного zipfile
Работа с zip-архивами с помощью стандартного zipfile

Изучение модуля hashlib: простое хеширование данных Когда речь заходит о безопасности и уникальной идентификации данных в Python, почти всегда всплывает модуль hashlib. Он не шифрует данные (это важно!), а превращает их в «отпечаток» — фиксированную строку, по которой нельзя восстановить оригинал, но можно проверить, не изменился ли он. ### Что такое хеш? Хеш-функция берет любые данные (строку, файл, пароль) и превращает их в строку фиксированной длины. Малейшее изменение исходных данных кардинально меняет хеш — это удобно для проверки целостности. ### Первый пример: хеш строки
import hashlib

text = "Hello, hashlib!"
text_bytes = text.encode("utf-8")  # хешируем всегда байты

hash_object = hashlib.sha256(text_bytes)
hash_hex = hash_object.hexdigest()

print(hash_hex)
sha256 — один из популярных алгоритмов. Метод .hexdigest() возвращает хеш в виде удобной шестнадцатеричной строки. Попробуйте поменять хотя бы один символ в text — длина хеша останется той же, но значение полностью изменится. ### Проверка целостности данных Допустим, вы скачали файл и хотите убедиться, что он не поврежден. Принцип тот же — считаем хеш и сравниваем.
import hashlib

def file_hash(path, algorithm="sha256", chunk_size=8192):
    hash_obj = getattr(hashlib, algorithm)()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(chunk_size), b""):
            hash_obj.update(chunk)
    return hash_obj.hexdigest()

print(file_hash("example.txt"))
Здесь мы читаем файл по частям, чтобы не загружать его целиком в память (важно для больших файлов). ### Хеш пароля (но по‑простому) Подчеркиваю: это упрощенный подход для понимания, а не готовое решение для продакшена.
import hashlib

def hash_password(password: str, salt: str) -> str:
    data = (password + salt).encode("utf-8")
    return hashlib.sha256(data).hexdigest()

password_hash = hash_password("my_secret_password", "random_salt_123")
print(password_hash)
Зачем salt? Без него одинаковые пароли у разных пользователей давали бы одинаковые хеши. Соль делает результат уникальным. ### Быстрый обзор алгоритмов
import hashlib
print(hashlib.algorithms_guaranteed)
Самые используемые: md5, sha1, sha224, sha256, sha384, sha512. Для новых проектов md5 и sha1 уже считаются слабыми для безопасности, но всё ещё полезны для простой проверки целостности (там, где нет угрозы взлома). --- hashlib — отличный инструмент, чтобы аккуратно и предсказуемо «упаковывать» данные в компактные отпечатки. Понимание хеширования — обязательный шаг на пути к более продвинутым темам: аутентификация, токены, работа с файлами и безопасностью в целом.

Изучение модуля hashlib: простое хеширование данных
Изучение модуля hashlib: простое хеширование данных

### Python для начинающих: создаем красивый прогресс-бар с tqdm Когда скрипт «думает» дольше пары секунд, пользователь начинает волноваться: «Он вообще работает или уже завис?». Прогресс-бар в терминале решает эту проблему — и модуль tqdm делает это буквально одной строкой кода. --- ## Установка tqdm не входит в стандартную библиотеку, его нужно установить:
pip install tqdm
--- ## Базовый пример Минимальный прогресс-бар для цикла:
from time import sleep
from tqdm import tqdm

for i in tqdm(range(100)):
    sleep(0.05)  # имитация работы
Что происходит: - tqdm(iterable) оборачивает любой итерируемый объект (range, список, генератор); - выводит прогресс, процент, скорость и время до окончания. --- ## Прогресс-бар для списков и генераторов tqdm не требует именно range. Можно использовать списки:
from time import sleep
from tqdm import tqdm

files = ["data1.csv", "data2.csv", "data3.csv"]

for filename in tqdm(files, desc="Processing"):
    sleep(0.5)  # обработка файла
Аргумент desc добавляет понятное описание слева от прогресс-бара. --- ## Ручное обновление прогресса Если у вас нет удобного итератора (например, цикл внутри функции), можно обновлять прогресс вручную:
from time import sleep
from tqdm import tqdm

total_steps = 5

with tqdm(total=total_steps, desc="Custom task") as pbar:
    for step in range(total_steps):
        sleep(0.7)  # тяжелая операция
        pbar.update(1)
Здесь: - total — общее количество шагов; - pbar.update(n) — сообщаем, что выполнено n шагов. --- ## Обёртка для функций: tqdm + map Если вы обрабатываете коллекцию с помощью map, можно добавить прогресс почти без изменений кода:
from time import sleep
from tqdm import tqdm

def process_item(item):
    sleep(0.2)
    return item * 2

items = list(range(50))
results = list(tqdm(map(process_item, items), total=len(items)))
Важный момент: для map нужно явно указать total=len(items), иначе tqdm не знает, сколько всего элементов. --- ## Быстрый чек-лист по tqdm - Подходит для любых итерируемых объектов. - Умеет считать время, скорость и ETA. - Можно использовать как контекстный менеджер и обновлять вручную. - Практически не требует изменений логики программы. Попробуйте добавить tqdm в ваш текущий скрипт с долгими циклами — и терминал сразу станет дружелюбнее для пользователя… и для вас.