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. Чтение бинарного файла по записям
Представим файл, состоящий из записей:
- timestamp — double (8 байт)
- value — float (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 и жесткими бинарными протоколами. Если вы когда-нибудь захотите написать свой формат файла или разобрать чужой, этот модуль станет вашим основным инструментом.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 — это не про «сложность ради сложности», а про то, чтобы один раз описать универсальную логику и дальше безопасно переиспользовать её с разными типами.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 — это маленький, но очень полезный инструмент, когда вам нужно много раз искать и вставлять элементы в отсортированный список без лишних затрат и переписывания алгоритмов бинарного поиска вручную.Работа с логами: запись и фильтрация с модулем
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() и хаоса в консоли.Сериализация с
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 это мощный и гибкий инструмент.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 — это не «костыль», а нормальная архитектура. Она превращает хрупкие скрипты в программы, с которыми можно работать без страха, что любой неправильный ввод всё сломает.Создание простого 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)
### Как использовать
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 — отличный инструмент “вежливой строгости”: код работает, но пользователь заранее узнает, где его может ждать сюрприз.
现已上线!2025 年 Telegram 研究 — 年度关键洞察 
