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

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

رفتن به کانال در Telegram

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

نمایش بیشتر
1 239
مشترکین
اطلاعاتی وجود ندارد24 ساعت
+17 روز
-130 روز
آرشیو پست ها
### Работа с фиксированными структурами данных с помощью struct Когда Pythonу приходится общаться с «железом» или чужим бинарным форматом (сетевой протокол, файл игры, формат датасета), удобные списки и словари уже не спасают. Там правит мир байтов и фиксированных структур. Для таких задач в стандартной библиотеке есть модуль struct. --- ## Зачем нужен struct struct умеет: - упаковывать Python-значения в байтовую строку фиксированного формата; - распаковывать байты обратно в числа, строки и т.п.; - контролировать порядок байт (big-endian, little-endian) и выравнивание. Формат описывается строкой: типы идут подряд: - i — 4-байтовое целое (int) - f — 4-байтовый float - d — 8-байтовый float (double) - h — 2-байтовое целое (short) - s — строка фиксированной длины (10s — 10 байт) - префикс > — big-endian, < — little-endian, ! — сетевой порядок (big-endian) --- ## Пример 1. Упаковка заголовка файла Допустим, у нас есть простой бинарный формат: - magic — 4 байта, строка - version — 1 байт, число - records_count — 4 байта, число (little-endian)
import struct

fmt = "<4sBI"  # 4s - 4 байта строки, B - unsigned char, I - unsigned int

magic = b"DATA"
version = 1
records_count = 42

header_bytes = struct.pack(fmt, magic, version, records_count)
print(header_bytes)         # b'DATA\x01*\x00\x00\x00'
print(len(header_bytes))    # 9 байт
А теперь распакуем:
unpacked = struct.unpack(fmt, header_bytes)
magic_u, version_u, records_count_u = unpacked
print(magic_u, version_u, records_count_u)
Важно: struct всегда возвращает кортеж — даже если значение одно. --- ## Пример 2. Фиксированная строка в структуре Частая задача: записать имя пользователя длиной ровно 16 байт.
import struct

fmt = "<I16s"  # I - id, 16s - имя фиксированной длины

user_id = 7
name = "Alice"
name_bytes = name.encode("utf-8")
name_padded = name_bytes.ljust(16, b"\x00")  # добиваем нулями

packed = struct.pack(fmt, user_id, name_padded)
print(packed)

user_id_u, raw_name = struct.unpack(fmt, packed)
name_u = raw_name.rstrip(b"\x00").decode("utf-8")
print(user_id_u, name_u)
Так обрабатываются C-подобные структуры, где строки всегда занимают ровно N байт. --- ## Пример 3. Чтение бинарного файла по записям Представим файл, состоящий из записей: - timestampdouble (8 байт) - valuefloat (4 байта)
import struct

record_fmt = "<df"
record_size = struct.calcsize(record_fmt)

def iter_records(path):
    with open(path, "rb") as f:
        while chunk := f.read(record_size):
            if len(chunk) < record_size:
                break
            yield struct.unpack(record_fmt, chunk)

for ts, value in iter_records("data.bin"):
    print(ts, value)
struct.calcsize гарантирует, что вы читаете ровно столько байт, сколько занимает одна структура. --- struct — это мост между удобным миром Python и жесткими бинарными протоколами. Если вы когда-нибудь захотите написать свой формат файла или разобрать чужой, этот модуль станет вашим основным инструментом.

Работа с фиксированными структурами данных с помощью struct
Работа с фиксированными структурами данных с помощью struct

Generics в Python: зачем они нужны и как ими пользоваться Если вы уже видели List[int] или Dict[str, float] и подумали: «Окей, типы, понятно», — то generics (обобщения) — следующий шаг. С их помощью вы можете описывать свои универсальные типы так же, как это делает стандартная библиотека. --- ### Зачем нужны generics Generics позволяют описать шаблон типа: однажды задаёте логику, а потом подставляете конкретные типы. Например, у вас есть класс Box, который хранит «что угодно», но вы хотите, чтобы для конкретного использования Python (и IDE) знали, что именно внутри: Box[int], Box[str] и т.д. --- ### Базовый пример с TypeVar Минимальный кирпичик generics — TypeVar:
from typing import TypeVar, List

T = TypeVar("T")

def first(items: List[T]) -> T | None:
    if not items:
        return None
    return items[0]

nums = first([1, 2, 3])         # -> int | None
names = first(["a", "b", "c"])  # -> str | None
Функция одна, логика одна, но тип результата зависит от типа элементов списка. --- ### Обобщённый класс Сделаем свой Box, который «знает», что он хранит:
from typing import Generic, TypeVar

T = TypeVar("T")

class Box(Generic[T]):
    def __init__(self, value: T) -> None:
        self.value = value

    def get(self) -> T:
        return self.value

    def set(self, value: T) -> None:
        self.value = value

user_box = Box[str]("admin")
age_box = Box[int](42)

reveal_user = user_box.get()  # str
reveal_age = age_box.get()    # int
Box[T] — это шаблон. А Box[int], Box[str] — уже конкретные типы. --- ### Ограничения типов (bound и constraints) Иногда нужно разрешить не «любой тип», а только наследников какого-то базового класса.
from typing import TypeVar, Protocol

class SupportsId(Protocol):
    id: int

U = TypeVar("U", bound=SupportsId)

def get_id(obj: U) -> int:
    return obj.id
Теперь get_id принимает только объекты, у которых есть id: int. --- ### Generic по ключам и значениям Можно описывать несколько параметров типов:
from typing import TypeVar, Dict, Generic

K = TypeVar("K")
V = TypeVar("V")

class SimpleCache(Generic[K, V]):
    def __init__(self) -> None:
        self._data: Dict[K, V] = {}

    def set(self, key: K, value: V) -> None:
        self._data[key] = value

    def get(self, key: K) -> V | None:
        return self._data.get(key)

user_cache = SimpleCache[int, str]()
user_cache.set(1, "Alice")
name = user_cache.get(1)  # str | None
--- ### Где это реально помогает - IDE подсказывает корректные типы в ваших обобщённых функциях и классах. - Mypy/pyright ловят ошибки: Box[int] уже не позволит положить внутрь строку. - Код становится самодокументируемым: сигнатура явно показывает, что функция/класс универсальны. Generics — это не про «сложность ради сложности», а про то, чтобы один раз описать универсальную логику и дальше безопасно переиспользовать её с разными типами.

Как использовать generics в Python с typing
Как использовать generics в Python с typing

Python для начинающих: как приручить модули и относительные импорты Когда скрипт вырастает до 300+ строк, его уже страшно открывать, не то что править. Решение простое и взрослое — разбивать код на модули и пакеты. Давай разберёмся, как это делать аккуратно и зачем вообще нужны относительные импорты. --- ## Модули и пакеты по-простому - Модуль — любой .py файл. - Пакет — папка с файлом __init__.py (может быть пустым). Пример структуры проекта:
my_project/
    app.py
    core/
        __init__.py
        models.py
        services.py
        utils/
            __init__.py
            validators.py
Теперь core и core.utils — это пакеты, а остальные .py — модули. --- ## Абсолютный импорт Самый прямолинейный способ:
# app.py
from core.models import User
from core.services import UserService
from core.utils.validators import validate_email
Плюс: ясно, откуда что приезжает. Минус: если переименуешь пакет core в backend, придется править полпроекта. --- ## Зачем нужны относительные импорты Внутри пакета можно ссылаться на «соседей» относительно текущего модуля, а не от корня проекта. Используются точки: - . — текущий пакет - .. — на уровень вверх - ... — на два уровня вверх и т.д. ### Пример
# core/services.py
from .models import User          # из того же пакета core
from .utils.validators import validate_email  # из подпакета utils
# core/utils/validators.py
from ..models import User  # поднялись из core/utils в core и взяли models
Это удобно, если структура пакетов логична сама по себе и ты не хочешь, чтобы имя «корневого» пакета было зашито в каждый импорт. --- ## Важный момент: как запускать проект Относительные импорты корректно работают, если запускать код как модуль, а не «проваливаться» внутрь пакета. Правильно:
python -m my_project.app
Неправильно:
cd my_project/core
python services.py  # относительные импорты могут упасть
Когда запускаешь модуль через -m, Python понимает структуру пакетов и не путается с путями. --- ## Практический мини-шаблон
# core/models.py
class User:
    def __init__(self, email: str):
        self.email = email
# core/utils/validators.py
def validate_email(email: str) -> bool:
    return "@" in email and "." in email
# core/services.py
from .models import User
from .utils.validators import validate_email

def create_user(email: str) -> User:
    if not validate_email(email):
        raise ValueError("Invalid email")
    return User(email=email)
# app.py
from core.services import create_user

if __name__ == "__main__":
    user = create_user("test@example.com")
    print(user.email)
--- Главная идея: модули — это способ разрезать код на логические куски, а относительные импорты — способ связывать эти куски так, чтобы проект можно было безболезненно переорганизовывать. Как только твой файл перестанет влезать «на один экран», самое время завести свой первый пакет.

Организация кода в виде модулей и импорт с относительными путями
Организация кода в виде модулей и импорт с относительными путями

Создание простого таймера обратного отсчёта на Python Таймер обратного отсчёта — отличный мини‑проект для новичка: он знакомит с циклами, модулями стандартной библиотеки и работой со временем. А ещё его реально можно использовать: для помодоро-сессий, тренировок или напоминаний. --- ### Вариант 1: Самый простой консольный таймер Используем модуль time и цикл while:
import time

def countdown(seconds):
    while seconds > 0:
        mins = seconds // 60
        secs = seconds % 60
        time_format = f"{mins:02d}:{secs:02d}"
        print(time_format, end="\r")
        time.sleep(1)
        seconds -= 1
    print("00:00")
    print("Time is up!")

countdown(10)
Что здесь важно: - time.sleep(1) — «усыпляет» программу на 1 секунду. - end="\r" — перезаписывает строку в консоли, вместо того чтобы печатать новую. - Формат f"{mins:02d}" добавляет ведущий ноль (например, 03:07). Попробуйте поменять countdown(10) на любое количество секунд. --- ### Вариант 2: Таймер с вводом от пользователя Добавим интерактивность:
import time

def countdown(seconds):
    while seconds > 0:
        mins = seconds // 60
        secs = seconds % 60
        print(f"{mins:02d}:{secs:02d}", end="\r")
        time.sleep(1)
        seconds -= 1
    print("00:00")
    print("Time is up!")

user_input = int(input("Enter time in seconds: "))
countdown(user_input)
Здесь мы получаем число секунд от пользователя и передаём его в функцию. Если хотите, можете добавить проверку ввода, чтобы обрабатывать нечисловые значения и отрицательные числа. --- ### Вариант 3: Таймер до конкретного момента времени Теперь немного «прокачаемся» и используем модуль datetime: сделаем обратный отсчёт до заданного времени в будущем.
import time
from datetime import datetime, timedelta

def countdown_to(target_time):
    while True:
        now = datetime.now()
        delta = target_time - now
        if delta.total_seconds() <= 0:
            print("00:00:00")
            print("Time is up!")
            break
        total_seconds = int(delta.total_seconds())
        hours = total_seconds // 3600
        mins = (total_seconds % 3600) // 60
        secs = total_seconds % 60
        print(f"{hours:02d}:{mins:02d}:{secs:02d}", end="\r")
        time.sleep(1)

target = datetime.now() + timedelta(minutes=1)
countdown_to(target)
Здесь: - datetime.now() — текущее время. - timedelta(minutes=1) — интервал в 1 минуту. - delta.total_seconds() — сколько секунд осталось. --- На таком простом таймере вы затрагиваете сразу несколько фундаментальных вещей: циклы, функции, работу с модулями time и datetime, форматирование строк и простую логику. Это именно тот тип мини‑проекта, который помогает быстро почувствовать живой результат от кода.

Создание простого таймера обратного отсчета
Создание простого таймера обратного отсчета

Введение в модуль bisect: быстрые операции с отсортированными списками Когда вы работаете с отсортированным списком, вставка новых элементов может испортить порядок — приходится либо искать позицию вручную, либо каждый раз вызывать sort(). Модуль bisect решает эту проблему: он помогает искать позицию для элемента в отсортированном списке и вставлять его так, чтобы порядок сохранился. --- ## Основные функции bisect Импортируем модуль:
import bisect
Четыре ключевые функции: - bisect_left(a, x) — найти позицию для вставки x слева (перед равными элементами) - bisect_right(a, x) или просто bisect.bisect(a, x) — позиция для вставки справа (после равных) - insort_left(a, x) — вставить x слева и сохранить сортировку - insort_right(a, x) или insort(a, x) — вставить x справа --- ## Поиск позиции без ручных циклов
import bisect

scores = [10, 20, 20, 30, 40]

pos_left = bisect.bisect_left(scores, 20)
pos_right = bisect.bisect_right(scores, 20)

print(pos_left)   # 1  -> перед первой 20
print(pos_right)  # 3  -> после последней 20
Так вы мгновенно узнаете, куда встанет элемент, не перебирая список вручную. --- ## Вставка в отсортированный список Вставляем и сразу поддерживаем порядок:
import bisect

scores = [10, 20, 30, 40]

bisect.insort(scores, 25)
bisect.insort(scores, 30)

print(scores)  # [10, 20, 25, 30, 30, 40]
insort делает insert + бинарный поиск под капотом. Для вас это одна строка — и список по-прежнему отсортирован. --- ## Реальный пример: поиск "позиции в рейтинге" Допустим, у вас есть отсортированный список результатов, и вы хотите узнать, каким по счету будет новый результат:
import bisect

high_scores = [100, 90, 80, 70, 60]
high_scores.sort(reverse=True)  # гарантируем порядок по убыванию

# Для работы с bisect развернем по возрастанию
asc_scores = sorted(high_scores)

new_score = 85
pos = len(asc_scores) - bisect.bisect_left(asc_scores, new_score)

print(f"New score rank: {pos}")  # позиция в рейтинге по убыванию
--- Модуль bisect — это маленький, но очень полезный инструмент, когда вам нужно много раз искать и вставлять элементы в отсортированный список без лишних затрат и переписывания алгоритмов бинарного поиска вручную.

Введение в модуль bisect для работы с отсортированными списками
Введение в модуль bisect для работы с отсортированными списками

Работа с логами: запись и фильтрация с модулем logging В любой программе рано или поздно наступает момент, когда print() перестает помогать. Нужно понимать, что происходит “под капотом”, но не засорять консоль сотней строк. Здесь на сцену выходит модуль logging. ### Базовая настройка логов Начнем с простого: запишем логи в файл и выведем их в консоль.
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    handlers=[
        logging.FileHandler("app.log", encoding="utf-8"),
        logging.StreamHandler()
    ]
)

logger = logging.getLogger("app")

logger.debug("Debug message")     # не увидим, уровень ниже INFO
logger.info("App started")
logger.warning("Low disk space")
logger.error("Unexpected error")
Ключевые идеи: - level — минимальный уровень сообщений (DEBUG, INFO, WARNING, ERROR, CRITICAL). - handlers — куда отправлять логи (файл, консоль, сеть и т.п.). - format — шаблон строки лога: время, уровень, имя логгера, сообщение. ### Логи по модулям Для реальных приложений полезно иметь отдельный логгер на модуль:
# module_a.py
import logging
logger = logging.getLogger("module_a")

def run_task():
    logger.info("Task started")
    logger.debug("Internal details...")
# main.py
import logging
import module_a

logging.basicConfig(level=logging.DEBUG)
module_a.run_task()
Теперь ты можешь тонко управлять шумом:
logging.getLogger("module_a").setLevel(logging.WARNING)
Весь проект логируется детально, но module_a пишет только предупреждения и ошибки. ### Фильтрация логов Иногда нужен не только уровень, но и умный фильтр. Например, логировать только запросы к критичным endpoint’ам.
import logging

class EndpointFilter(logging.Filter):
    def filter(self, record):
        return "/payments" in record.getMessage()

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

handler = logging.FileHandler("payments.log", encoding="utf-8")
handler.addFilter(EndpointFilter())
logger.addHandler(handler)

logger.info("GET /status 200")
logger.info("POST /payments 500")  # только это уйдет в payments.log
Фильтры позволяют: - разделять логи по файлам по любому условию; - временно “выключать” шумный функционал; - собирать отдельные журналы для аудита. ### Логирование исключений Еще один must-have — логировать трассировку стека:
import logging

logging.basicConfig(level=logging.ERROR)
logger = logging.getLogger("errors")

try:
    1 / 0
except ZeroDivisionError:
    logger.exception("Calculation failed")
logger.exception автоматически добавит traceback к сообщению — незаменимо при отладке. --- Модуль logging — это ваш “черный ящик” приложения. Настроив уровни, обработчики и фильтры, вы получаете полную картину происходящего без тонны print() и хаоса в консоли.

Работа с логами: запись и фильтрация с модулем logging
Работа с логами: запись и фильтрация с модулем logging

Сериализация с pickle: сохраняем объекты Python “в банку” Иногда хочется просто “заморозить” объект Python: сложный словарь, результат обучения модели, кэш вычислений — а потом в другом месте и в другое время “разморозить” его и продолжить работу. Для этого есть модуль pickle. Сериализация — это превращение объекта в последовательность байт. Десериализация — обратный процесс: из байтов снова получаем объект. pickle умеет сохранять почти всё: списки, словари, классы, функции (с нюансами), вложенные структуры. Минус — формат бинарный и специфичный для Python, то есть: - файлы непонятны другим языкам; - нельзя бездумно грузить непроверенный pickle-файл — это небезопасно. --- ### Базовый пример: сохранить и загрузить объект
import pickle

data = {
    "users": ["alice", "bob"],
    "settings": {"theme": "dark", "volume": 80},
    "active": True
}

# сериализация в файл
with open("data.pkl", "wb") as f:
    pickle.dump(data, f)

# десериализация из файла
with open("data.pkl", "rb") as f:
    loaded_data = pickle.load(f)

print(loaded_data)
dump/load работают с файлами. Обрати внимание на режим: "wb" и "rb" — бинарный. --- ### Работа в памяти: dumps и loads Если нужно получить байты, например, чтобы отправить по сети или положить в базу:
import pickle

config = {"timeout": 10, "retries": 3}

serialized = pickle.dumps(config)   # bytes
print(type(serialized), len(serialized))

restored = pickle.loads(serialized)
print(restored)
--- ### Сериализация собственных классов pickle умеет сохранять экземпляры пользовательских классов (если они определены на верхнем уровне модуля):
import pickle

class Player:
    def __init__(self, name, score):
        self.name = name
        self.score = score

player = Player("Alice", 42)

with open("player.pkl", "wb") as f:
    pickle.dump(player, f)

with open("player.pkl", "rb") as f:
    loaded_player = pickle.load(f)

print(loaded_player.name, loaded_player.score)
Важно: при загрузке класс Player должен быть доступен (импортирован) с тем же именем и в том же модуле. --- ### Вопрос безопасности pickle.load может выполнить произвольный код, если файл злонамеренно подделан. Поэтому правило простое и жесткое: Никогда не загружай pickle-файлы из непроверенных источников. Для обмена данными между разными системами чаще выбирают JSON. Но когда нужно быстро и удобно сохранять сложные Python-объекты “для себя” — pickle это мощный и гибкий инструмент.

Сериализация и десериализация данных с pickle
Сериализация и десериализация данных с pickle

Python для начинающих: приручаем ошибки ввода с помощью try / except Любая программа, в которую что‑то вводит человек, рано или поздно ломается. Пользователь вместо числа вводит "пять", вместо пути к файлу — пустую строку, а вместо выбора меню — пробел. Если не обработать такие ситуации, программа падает с некрасивым трейсбеком. В Python для этого есть связка try / except. Идея проста: «Попробуй выполнить этот код. Если случится ошибка — поймай её и сделай что‑нибудь разумное». --- ### Базовый пример: просим число у пользователя
while True:
    user_input = input("Enter an integer: ")
    try:
        value = int(user_input)
        break
    except ValueError:
        print("This is not a valid integer, try again.")

print("You entered:", value)
Что здесь происходит: - try: пытаемся преобразовать строку в int. - except ValueError: ловим конкретную ошибку, когда строка не похожа на число. - Цикл повторяется, пока пользователь не введет корректное значение. Такой подход спасает от падения программы и делает поведение предсказуемым. --- ### Несколько исключений и «план Б» по умолчанию Иногда полезно различать разные типы ошибок:
def safe_division(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        print("Division by zero is not allowed.")
        return None
    except TypeError:
        print("Both arguments must be numbers.")
        return None

print(safe_division(10, 0))
print(safe_division(10, "x"))
- Мы обрабатываем две разные ошибки по‑разному. - Вместо аварийного завершения возвращаем None и осмысленное сообщение. --- ### Добавляем else и finally else выполняется, если в try не было ошибок. finally выполняется всегда — даже при ошибке или return.
def read_int(prompt):
    while True:
        user_input = input(prompt)
        try:
            value = int(user_input)
        except ValueError:
            print("Invalid number, try again.")
        else:
            print("Thanks, got a valid number.")
            return value
        finally:
            # Можно использовать для логирования, очистки ресурсов и т.п.
            pass
--- ### Практический мини‑паттерн: «надёжный ввод с попытками»
def ask_int_limited(prompt, attempts=3):
    for _ in range(attempts):
        try:
            return int(input(prompt))
        except ValueError:
            print("Please enter a valid integer.")
    raise ValueError("Too many invalid attempts.")

age = ask_int_limited("Enter your age: ")
print("Age is:", age)
Здесь мы: - ограничиваем количество попыток, - при превышении — явно выбрасываем исключение с понятным текстом. --- Обработка ошибок с try / except — это не «костыль», а нормальная архитектура. Она превращает хрупкие скрипты в программы, с которыми можно работать без страха, что любой неправильный ввод всё сломает.

Обработка ошибок ввода пользователя с try / except
Обработка ошибок ввода пользователя с try / except

Создание простого API с BaseHTTPServer / http.server Когда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк. Ниже — современный вариант на Python 3 с модулем http.server. Для Python 2 можно использовать те же идеи, просто заменить импорты. ### Мини‑сервер на http.server Сделаем API, у которого есть два маршрута: - GET / — вернёт простое сообщение - GET /api/time — вернёт текущее время в JSON
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
from datetime import datetime


class SimpleAPIHandler(BaseHTTPRequestHandler):
    def _set_headers(self, status=200, content_type="application/json"):
        self.send_response(status)
        self.send_header("Content-Type", content_type)
        self.end_headers()

    def do_GET(self):
        if self.path == "/":
            self._set_headers(content_type="text/plain; charset=utf-8")
            self.wfile.write(b"Welcome to Simple API")
        elif self.path == "/api/time":
            self._set_headers()
            data = {"time": datetime.utcnow().isoformat() + "Z"}
            self.wfile.write(json.dumps(data).encode("utf-8"))
        else:
            self._set_headers(status=404)
            data = {"error": "Not found"}
            self.wfile.write(json.dumps(data).encode("utf-8"))


def run_server(host="127.0.0.1", port=8000):
    server_address = (host, port)
    httpd = HTTPServer(server_address, SimpleAPIHandler)
    print(f"Serving on http://{host}:{port}")
    httpd.serve_forever()


if __name__ == "__main__":
    run_server()
Запусти этот скрипт и проверь: - http://127.0.0.1:8000/ - http://127.0.0.1:8000/api/time или из терминала:
curl http://127.0.0.1:8000/api/time
### А как в Python 2? Логика та же, только модуль другой:
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
и без encode("utf-8"), там всё байтовое по умолчанию. Но лучше всё‑таки ориентироваться на Python 3 — Python 2 уже давно снят с поддержки. ### Что полезно заметить - BaseHTTPRequestHandler даёт тебе полный контроль над протоколом — идеально, чтобы понять, как на самом деле работает HTTP. - Метод do_GET вызывается для запросов GET. Аналогично можно реализовать do_POST, do_PUT, do_DELETE. - self.path — это путь из URL, на нём легко построить свою мини‑маршрутизацию без фреймворков. Такой «ручной» подход не заменяет Flask или FastAPI, но отлично прокачивает понимание основ и помогает быстро накидать простое внутреннее API или заглушку для тестов.

Создание простого API с модулем BaseHTTPServer (Python 2) или http.server (Python 3)
Создание простого API с модулем BaseHTTPServer (Python 2) или http.server (Python 3)

### Как использовать warnings для генерации пользовательских предупреждений В Python многие разработчики знают про исключения, но гораздо реже используют предупреждения. А зря: модуль warnings позволяет мягко сообщить пользователю о проблеме, не ломая выполнение программы. Предупреждение — это сигнал: “что‑то не так, но мы еще можем продолжать”. --- ## Базовый пример: ваше первое предупреждение
import warnings

def divide(a, b):
    if b == 0:
        warnings.warn("Division by zero replaced with 0", RuntimeWarning)
        return 0
    return a / b

print(divide(10, 0))
Здесь вместо аварийного ZeroDivisionError мы возвращаем безопасное значение и генерируем предупреждение. Тип RuntimeWarning помогает понять характер проблемы. --- ## Свой класс предупреждений Как и с исключениями, вы можете создавать свои типы:
import warnings

class ConfigWarning(UserWarning):
    pass

def load_config(config):
    if "timeout" not in config:
        warnings.warn("Missing 'timeout', using default = 30", ConfigWarning)
        return {**config, "timeout": 30}
    return config

cfg = load_config({"host": "example.com"})
Наследуемся от UserWarning — это стандартный базовый класс для пользовательских предупреждений. Так их легко отфильтровать или обработать отдельно. --- ## Управление показом предупреждений Иногда предупреждения слишком шумные. warnings позволяет управлять их поведением:
import warnings

warnings.filterwarnings(
    "ignore",              # действие: "ignore", "default", "error", "once", ...
    category=ConfigWarning # фильтрация по типу
)
Теперь все ConfigWarning будут скрыты. А так можно превратить предупреждение в исключение — полезно для тестов:
warnings.filterwarnings("error", category=ConfigWarning)
Теперь любое ConfigWarning рухнет как ошибка. --- ## Локальное подавление предупреждений Если “шумит” только конкретный участок кода:
import warnings

with warnings.catch_warnings():
    warnings.simplefilter("ignore", category=ConfigWarning)
    load_config({"host": "example.com"})  # тут предупреждений не будет
Вне with поведение возвращается к обычному. --- ## Когда использовать предупреждения, а не исключения Предупреждения особенно полезны, когда: - используется устаревший параметр, но вы пока его поддерживаете; - подставляется значение по умолчанию вместо отсутствующего; - результат “подозрительно”, но еще пригоден; - вы готовите пользователей к будущим изменениям API. warnings — отличный инструмент “вежливой строгости”: код работает, но пользователь заранее узнает, где его может ждать сюрприз.

Как использовать warnings для генерации пользовательских предупреждений
Как использовать warnings для генерации пользовательских предупреждений