### Как использовать
В Python многие разработчики знают про исключения, но гораздо реже используют предупреждения. А зря: модуль
Предупреждение — это сигнал: “что‑то не так, но мы еще можем продолжать”.
---
## Базовый пример: ваше первое предупреждение
Здесь вместо аварийного
---
## Свой класс предупреждений
Как и с исключениями, вы можете создавать свои типы:
Наследуемся от
---
## Управление показом предупреждений
Иногда предупреждения слишком шумные.
Теперь все
Теперь любое
---
## Локальное подавление предупреждений
Если “шумит” только конкретный участок кода:
Вне
---
## Когда использовать предупреждения, а не исключения
Предупреждения особенно полезны, когда:
- используется устаревший параметр, но вы пока его поддерживаете;
- подставляется значение по умолчанию вместо отсутствующего;
- результат “подозрительно”, но еще пригоден;
- вы готовите пользователей к будущим изменениям API.
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 — отличный инструмент “вежливой строгости”: код работает, но пользователь заранее узнает, где его может ждать сюрприз.👍3
Создание простого API с
Когда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк.
Ниже — современный вариант на Python 3 с модулем
### Мини‑сервер на
Сделаем API, у которого есть два маршрута:
-
-
Запусти этот скрипт и проверь:
-
-
или из терминала:
### А как в Python 2?
Логика та же, только модуль другой:
и без
### Что полезно заметить
-
- Метод
-
Такой «ручной» подход не заменяет Flask или FastAPI, но отлично прокачивает понимание основ и помогает быстро накидать простое внутреннее API или заглушку для тестов.
BaseHTTPServer / http.serverКогда слышишь слово «API», кажется, что сейчас понадобятся Django, Flask и гора магии. Но под капотом всё начинается с очень простой идеи: принять HTTP‑запрос и вернуть HTTP‑ответ. В стандартной библиотеке Python уже есть всё, чтобы сделать мини‑API буквально в несколько строк.
Ниже — современный вариант на Python 3 с модулем
http.server. Для Python 2 можно использовать те же идеи, просто заменить импорты.### Мини‑сервер на
http.serverСделаем API, у которого есть два маршрута:
-
GET / — вернёт простое сообщение-
GET /api/time — вернёт текущее время в JSONfrom 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 или заглушку для тестов.
🔥3❤1👍1
Python для начинающих: приручаем ошибки ввода с помощью
Любая программа, в которую что‑то вводит человек, рано или поздно ломается. Пользователь вместо числа вводит
В 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 и finallyelse выполняется, если в 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 — это не «костыль», а нормальная архитектура. Она превращает хрупкие скрипты в программы, с которыми можно работать без страха, что любой неправильный ввод всё сломает.👍2
Сериализация с
Иногда хочется просто “заморозить” объект Python: сложный словарь, результат обучения модели, кэш вычислений — а потом в другом месте и в другое время “разморозить” его и продолжить работу. Для этого есть модуль
Сериализация — это превращение объекта в последовательность байт. Десериализация — обратный процесс: из байтов снова получаем объект.
- файлы непонятны другим языкам;
- нельзя бездумно грузить непроверенный pickle-файл — это небезопасно.
---
### Базовый пример: сохранить и загрузить объект
---
### Работа в памяти:
Если нужно получить байты, например, чтобы отправить по сети или положить в базу:
---
### Сериализация собственных классов
Важно: при загрузке класс
---
### Вопрос безопасности
Никогда не загружай pickle-файлы из непроверенных источников.
Для обмена данными между разными системами чаще выбирают JSON. Но когда нужно быстро и удобно сохранять сложные Python-объекты “для себя” —
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 это мощный и гибкий инструмент.👍3
Работа с логами: запись и фильтрация с модулем
В любой программе рано или поздно наступает момент, когда
### Базовая настройка логов
Начнем с простого: запишем логи в файл и выведем их в консоль.
Ключевые идеи:
-
-
-
### Логи по модулям
Для реальных приложений полезно иметь отдельный логгер на модуль:
Теперь ты можешь тонко управлять шумом:
Весь проект логируется детально, но
### Фильтрация логов
Иногда нужен не только уровень, но и умный фильтр. Например, логировать только запросы к критичным endpoint’ам.
Фильтры позволяют:
- разделять логи по файлам по любому условию;
- временно “выключать” шумный функционал;
- собирать отдельные журналы для аудита.
### Логирование исключений
Еще один must-have — логировать трассировку стека:
---
Модуль
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() и хаоса в консоли.👍2❤1🔥1
Введение в модуль
Когда вы работаете с отсортированным списком, вставка новых элементов может испортить порядок — приходится либо искать позицию вручную, либо каждый раз вызывать
---
## Основные функции
Импортируем модуль:
Четыре ключевые функции:
-
-
-
-
---
## Поиск позиции без ручных циклов
Так вы мгновенно узнаете, куда встанет элемент, не перебирая список вручную.
---
## Вставка в отсортированный список
Вставляем и сразу поддерживаем порядок:
---
## Реальный пример: поиск "позиции в рейтинге"
Допустим, у вас есть отсортированный список результатов, и вы хотите узнать, каким по счету будет новый результат:
---
Модуль
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 — это маленький, но очень полезный инструмент, когда вам нужно много раз искать и вставлять элементы в отсортированный список без лишних затрат и переписывания алгоритмов бинарного поиска вручную.🔥2
Создание простого таймера обратного отсчёта на Python
Таймер обратного отсчёта — отличный мини‑проект для новичка: он знакомит с циклами, модулями стандартной библиотеки и работой со временем. А ещё его реально можно использовать: для помодоро-сессий, тренировок или напоминаний.
---
### Вариант 1: Самый простой консольный таймер
Используем модуль
Что здесь важно:
-
-
- Формат
Попробуйте поменять
---
### Вариант 2: Таймер с вводом от пользователя
Добавим интерактивность:
Здесь мы получаем число секунд от пользователя и передаём его в функцию. Если хотите, можете добавить проверку ввода, чтобы обрабатывать нечисловые значения и отрицательные числа.
---
### Вариант 3: Таймер до конкретного момента времени
Теперь немного «прокачаемся» и используем модуль
Здесь:
-
-
-
---
На таком простом таймере вы затрагиваете сразу несколько фундаментальных вещей: циклы, функции, работу с модулями
Таймер обратного отсчёта — отличный мини‑проект для новичка: он знакомит с циклами, модулями стандартной библиотеки и работой со временем. А ещё его реально можно использовать: для помодоро-сессий, тренировок или напоминаний.
---
### Вариант 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, форматирование строк и простую логику. Это именно тот тип мини‑проекта, который помогает быстро почувствовать живой результат от кода.👍6
Python для начинающих: как приручить модули и относительные импорты
Когда скрипт вырастает до 300+ строк, его уже страшно открывать, не то что править. Решение простое и взрослое — разбивать код на модули и пакеты. Давай разберёмся, как это делать аккуратно и зачем вообще нужны относительные импорты.
---
## Модули и пакеты по-простому
- Модуль — любой
- Пакет — папка с файлом
Пример структуры проекта:
Теперь
---
## Абсолютный импорт
Самый прямолинейный способ:
Плюс: ясно, откуда что приезжает.
Минус: если переименуешь пакет
---
## Зачем нужны относительные импорты
Внутри пакета можно ссылаться на «соседей» относительно текущего модуля, а не от корня проекта.
Используются точки:
-
-
-
### Пример
Это удобно, если структура пакетов логична сама по себе и ты не хочешь, чтобы имя «корневого» пакета было зашито в каждый импорт.
---
## Важный момент: как запускать проект
Относительные импорты корректно работают, если запускать код как модуль, а не «проваливаться» внутрь пакета.
Правильно:
Неправильно:
Когда запускаешь модуль через
---
## Практический мини-шаблон
---
Главная идея: модули — это способ разрезать код на логические куски, а относительные импорты — способ связывать эти куски так, чтобы проект можно было безболезненно переорганизовывать. Как только твой файл перестанет влезать «на один экран», самое время завести свой первый пакет.
Когда скрипт вырастает до 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)
---
Главная идея: модули — это способ разрезать код на логические куски, а относительные импорты — способ связывать эти куски так, чтобы проект можно было безболезненно переорганизовывать. Как только твой файл перестанет влезать «на один экран», самое время завести свой первый пакет.
👍2🔥1
Generics в Python: зачем они нужны и как ими пользоваться
Если вы уже видели
---
### Зачем нужны generics
Generics позволяют описать шаблон типа: однажды задаёте логику, а потом подставляете конкретные типы. Например, у вас есть класс
---
### Базовый пример с
Минимальный кирпичик generics —
Функция одна, логика одна, но тип результата зависит от типа элементов списка.
---
### Обобщённый класс
Сделаем свой
---
### Ограничения типов (
Иногда нужно разрешить не «любой тип», а только наследников какого-то базового класса.
Теперь
---
### Generic по ключам и значениям
Можно описывать несколько параметров типов:
---
### Где это реально помогает
- IDE подсказывает корректные типы в ваших обобщённых функциях и классах.
- Mypy/pyright ловят ошибки:
- Код становится самодокументируемым: сигнатура явно показывает, что функция/класс универсальны.
Generics — это не про «сложность ради сложности», а про то, чтобы один раз описать универсальную логику и дальше безопасно переиспользовать её с разными типами.
Если вы уже видели
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 — это не про «сложность ради сложности», а про то, чтобы один раз описать универсальную логику и дальше безопасно переиспользовать её с разными типами.
👍2🔥1
### Работа с фиксированными структурами данных с помощью
Когда Pythonу приходится общаться с «железом» или чужим бинарным форматом (сетевой протокол, файл игры, формат датасета), удобные списки и словари уже не спасают. Там правит мир байтов и фиксированных структур. Для таких задач в стандартной библиотеке есть модуль
---
## Зачем нужен
- упаковывать Python-значения в байтовую строку фиксированного формата;
- распаковывать байты обратно в числа, строки и т.п.;
- контролировать порядок байт (big-endian, little-endian) и выравнивание.
Формат описывается строкой: типы идут подряд:
-
-
-
-
-
- префикс
---
## Пример 1. Упаковка заголовка файла
Допустим, у нас есть простой бинарный формат:
-
-
-
А теперь распакуем:
Важно:
---
## Пример 2. Фиксированная строка в структуре
Частая задача: записать имя пользователя длиной ровно 16 байт.
Так обрабатываются C-подобные структуры, где строки всегда занимают ровно N байт.
---
## Пример 3. Чтение бинарного файла по записям
Представим файл, состоящий из записей:
-
-
---
structКогда Pythonу приходится общаться с «железом» или чужим бинарным форматом (сетевой протокол, файл игры, формат датасета), удобные списки и словари уже не спасают. Там правит мир байтов и фиксированных структур. Для таких задач в стандартной библиотеке есть модуль
struct.---
## Зачем нужен
structstruct умеет:- упаковывать 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 и жесткими бинарными протоколами. Если вы когда-нибудь захотите написать свой формат файла или разобрать чужой, этот модуль станет вашим основным инструментом.❤2👍1
Создаем мини-базу данных на JSON: простой путь без SQL
Иногда для небольших проектов полноценная база данных — это как использовать экскаватор, чтобы посадить цветок. Для заметок, простого TODO, небольшого чата или прототипа вполне хватит обычного файла JSON. Давай сделаем свою мини-БД шаг за шагом.
---
### Почему JSON?
- Читается человеком (открыл файл — и сразу видно данные).
- Встроенная поддержка в Python (
- Легко переносить между проектами и языками.
---
### Базовые операции «мини-БД»
Нам нужны минимум 4 вещи:
1. Сохранить данные
2. Прочитать данные
3. Добавить запись
4. Найти запись по условию
Создадим файл
Теперь напишем функции для работы с записями. Пусть это будет мини-БД пользователей:
Использование:
---
### Добавляем «фишки»: фильтрация и обновление
Мини-БД станет полезнее, если можно будет:
- фильтровать по произвольному условию
- обновлять записи
Пример:
---
### Когда такой подход годится
Подходит, если:
- проект маленький;
- данных мало (до нескольких тысяч записей);
- не нужны сложные запросы и транзакции.
Если же требуется многопользовательский доступ, высокая нагрузка, сложная аналитика — тогда пора смотреть в сторону SQLite, PostgreSQL и других БД.
Но для начала изучения Python и быстрых прототипов JSON-«база» — отличный и наглядный инструмент, который ты полностью контролируешь.
Иногда для небольших проектов полноценная база данных — это как использовать экскаватор, чтобы посадить цветок. Для заметок, простого TODO, небольшого чата или прототипа вполне хватит обычного файла JSON. Давай сделаем свою мини-БД шаг за шагом.
---
### Почему JSON?
- Читается человеком (открыл файл — и сразу видно данные).
- Встроенная поддержка в Python (
json модуль).- Легко переносить между проектами и языками.
---
### Базовые операции «мини-БД»
Нам нужны минимум 4 вещи:
1. Сохранить данные
2. Прочитать данные
3. Добавить запись
4. Найти запись по условию
Создадим файл
db.json и модуль mini_db.py.# mini_db.py
import json
from pathlib import Path
DB_FILE = Path("db.json")
def load_data():
if not DB_FILE.exists():
return []
with DB_FILE.open("r", encoding="utf-8") as f:
return json.load(f)
def save_data(data):
with DB_FILE.open("w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
Теперь напишем функции для работы с записями. Пусть это будет мини-БД пользователей:
def add_user(name, age):
data = load_data()
new_id = (max((item["id"] for item in data), default=0) + 1)
user = {"id": new_id, "name": name, "age": age}
data.append(user)
save_data(data)
return user
def find_users_by_name(name):
data = load_data()
return [item for item in data if item["name"] == name]
Использование:
if __name__ == "__main__":
add_user("Alice", 25)
add_user("Bob", 30)
print(find_users_by_name("Alice"))
---
### Добавляем «фишки»: фильтрация и обновление
Мини-БД станет полезнее, если можно будет:
- фильтровать по произвольному условию
- обновлять записи
def filter_users(condition):
data = load_data()
return [item for item in data if condition(item)]
def update_user(user_id, **fields):
data = load_data()
updated = None
for item in data:
if item["id"] == user_id:
item.update(fields)
updated = item
break
if updated:
save_data(data)
return updated
Пример:
# Все пользователи старше 20
adults = filter_users(lambda u: u["age"] > 20)
# Обновляем возраст
update_user(1, age=26)
---
### Когда такой подход годится
Подходит, если:
- проект маленький;
- данных мало (до нескольких тысяч записей);
- не нужны сложные запросы и транзакции.
Если же требуется многопользовательский доступ, высокая нагрузка, сложная аналитика — тогда пора смотреть в сторону SQLite, PostgreSQL и других БД.
Но для начала изучения Python и быстрых прототипов JSON-«база» — отличный и наглядный инструмент, который ты полностью контролируешь.
👍2