Python примеры кода 2026 — практическое руководство на русском языке: разбор лучших примеров кода на Python, современные паттерны Python 3.12–3.15, чистый код, асинхронность, типизация, тестирование, продвинутые темы для middle+/senior и задачи для практики с решениями. Подходит для начинающих, junior-, middle- и senior-разработчиков, а также для подготовки к собеседованию по Python.
Ключевые темы: примеры кода Python, лучшие практики Python 2026, python для начинающих, задачи по Python с решениями, чистый код на Python, асинхронный Python, asyncio примеры, типизация в Python, pytest примеры, FastAPI пример, парсинг на Python, подготовка к собеседованию Python, Python 3.14 и 3.15 новые возможности, задачи для senior, дескрипторы, метаклассы, contextvars, free-threaded Python, t-строки, паттерны проектирования на Python.
🖥 Pythonl (канал 1, канал 2) — с помощью понятных картинок и коротких видео авторы объясняют сложные концепции и учат профессиональному подходу в разработке.
🖥 Python Интервью — огромное количество разобранных вопросов с реальных собеседований Python-разработчика.
🧠 Machine learning (канал 1, канал 2) — ИИ-инструменты для генерации Python-кода, умные агенты и всё, что нужно знать из области AI.
🔝 А здесь мы собрали целый кладезь полезных Python-ресурсов для прокачки.
- Почему Python в 2026 году
- Современный стек Python-разработчика 2026
- Разбор лучших примеров кода на Python
- Чистый и идиоматичный код
- Типизация: аннотации типов и дженерики
- Dataclasses и Pydantic
- Сопоставление с образцом (match/case)
- Асинхронность: asyncio и TaskGroup
- Работа с файлами через pathlib
- Обработка ошибок и ExceptionGroup
- Контекстные менеджеры и декораторы
- Генераторы и itertools
- Логирование вместо print
- Продвинутые темы Python (middle+ / senior)
- Дескрипторы
- Метапрограммирование:
__init_subclass__и метаклассы - Продвинутая типизация: PEP 695, Protocol, ParamSpec
- Паттерны asyncio: семафоры, очереди, retry, отмена
- Параллелизм: GIL, free-threading, субинтерпретаторы
- contextvars: контекст запроса в асинхронном коде
- Производительность и память
- Продвинутые генераторы и ExitStack
- Паттерны: Result, DI, Circuit Breaker
- Python 3.14 в коде: t-строки и annotationlib
- Что нового в Python 3.13, 3.14 и 3.15
- Практика: задачи по Python с решениями
- Мини-проекты для портфолио
- Тестирование: pytest примеры
- Частые ошибки новичков в Python
- Вопросы с собеседований по Python
- FAQ
- Полезные ресурсы
- Структура репозитория
Python остаётся одним из самых популярных языков программирования в мире. Его используют для:
- веб-разработки — FastAPI, Django, Litestar;
- анализа данных — pandas, Polars, DuckDB;
- машинного обучения и ИИ — PyTorch, scikit-learn, Transformers, LLM-агенты;
- автоматизации и скриптов — парсинг, боты, DevOps-утилиты;
- научных вычислений — NumPy, SciPy, Jupyter.
В 2026 году экосистема Python стала быстрее и удобнее: менеджер пакетов uv, линтер Ruff, быстрые тайпчекеры на Rust (ty, pyrefly), официально поддерживаемая сборка без GIL (free-threaded Python, с 3.14), t-строки, ленивые импорты (3.15) и более понятные сообщения об ошибках.
| Задача | Инструмент | Зачем |
|---|---|---|
| Управление версиями и зависимостями | uv |
Быстрая замена pip, venv, pip-tools и pyenv |
| Линтинг и форматирование | ruff |
Заменяет flake8, isort и black (ruff check, ruff format) |
| Проверка типов | mypy, pyright, ty (бета) |
Находит ошибки до запуска; ty от Astral — очень быстрый, на Rust |
| Тесты | pytest |
Стандарт де-факто для тестирования |
| Веб-API | FastAPI |
Асинхронный, с автодокументацией OpenAPI |
| Валидация данных | pydantic v2 |
Быстрая валидация на основе типов |
| HTTP-клиент | httpx |
Синхронный и асинхронный клиент |
| Таблицы и данные | polars, pandas |
Обработка табличных данных |
| Pre-commit хуки | pre-commit |
Автопроверки перед коммитом |
| Профилирование | profiling.sampling (3.15), py-spy |
Сэмплирующий профайлер, можно подключиться к работающему процессу |
| CI | GitHub Actions + astral-sh/setup-uv |
Тесты на нескольких версиях Python |
# установка uv (Linux/macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh
# новый проект
uv init my-project
cd my-project
# добавить зависимости
uv add httpx pydantic
uv add --dev pytest ruff mypy
# запуск
uv run main.py
uv run pytest
# проверки
uv run ruff check . && uv run ruff format .
uvx ty check # быстрый тайпчекер (бета)
# нужная версия Python, в том числе free-threaded сборка
uv python install 3.14 3.14t[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["httpx", "pydantic"]
[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B", "SIM"]Каждый пример построен по схеме: плохо → хорошо → почему.
Плохо:
result = []
for i in range(len(users)):
if users[i]["active"] == True:
result.append(users[i]["name"].upper())Хорошо:
active_names = [user["name"].upper() for user in users if user["active"]]Почему: list comprehension короче и быстрее, итерация идёт по элементам, а не по индексам, сравнение с True избыточно.
Ещё идиомы, которые стоит знать:
# enumerate вместо счётчика
for index, item in enumerate(items, start=1):
print(f"{index}. {item}")
# zip для параллельного обхода (strict=True ловит разную длину)
for name, score in zip(names, scores, strict=True):
print(f"{name}: {score}")
# распаковка
first, *middle, last = [1, 2, 3, 4, 5]
# словарь со значением по умолчанию
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
# ещё лучше — Counter
from collections import Counter
top = Counter(words).most_common(3)
# walrus-оператор :=
if (n := len(data)) > 100:
print(f"Слишком много данных: {n}")
# объединение словарей
config = defaults | user_settingsНачиная с Python 3.12 дженерики объявляются новым синтаксисом (PEP 695), а псевдонимы типов — через type.
from collections.abc import Iterable, Callable
type UserId = int
type JSON = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None
def first[T](items: Iterable[T], default: T | None = None) -> T | None:
"""Вернуть первый элемент или значение по умолчанию."""
return next(iter(items), default)
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
if not self._items:
raise IndexError("стек пуст")
return self._items.pop()
def apply_twice[T](func: Callable[[T], T], value: T) -> T:
return func(func(value))Почему: типы — это документация, которая проверяется автоматически (mypy, pyright) и даёт точное автодополнение в IDE.
Dataclass — для внутренних структур:
from dataclasses import dataclass, field
@dataclass(slots=True, frozen=True)
class Product:
name: str
price: float
tags: list[str] = field(default_factory=list)
@property
def price_with_vat(self) -> float:
return round(self.price * 1.2, 2)
p = Product("Клавиатура", 3500)
print(p.price_with_vat) # 4200.0Pydantic — для данных извне (API, JSON, конфиги):
from pydantic import BaseModel, EmailStr, Field
class UserIn(BaseModel):
name: str = Field(min_length=2, max_length=50)
email: EmailStr
age: int = Field(ge=0, le=150)
user = UserIn.model_validate({"name": "Анна", "email": "anna@example.com", "age": 28})
print(user.model_dump_json())Для
EmailStrустановитеpydantic[email].
Правило: dataclass — внутри приложения, pydantic — на границе с внешним миром.
def handle(event: dict) -> str:
match event:
case {"type": "click", "x": int(x), "y": int(y)}:
return f"Клик в точке ({x}, {y})"
case {"type": "key", "key": "Enter" | "Return"}:
return "Нажат Enter"
case {"type": "key", "key": str(key)}:
return f"Клавиша {key}"
case {"type": t}:
return f"Неизвестное событие: {t}"
case _:
return "Некорректные данные"
print(handle({"type": "click", "x": 10, "y": 20}))Почему: match/case заменяет длинные цепочки if/elif и одновременно проверяет структуру и извлекает значения.
Параллельная загрузка страниц через httpx и asyncio.TaskGroup (Python 3.11+):
import asyncio
import httpx
URLS = [
"https://www.python.org",
"https://docs.python.org/3/",
"https://pypi.org",
]
async def fetch(client: httpx.AsyncClient, url: str) -> tuple[str, int]:
response = await client.get(url, follow_redirects=True)
return url, response.status_code
async def main() -> None:
async with httpx.AsyncClient(timeout=10) as client:
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(fetch(client, url)) for url in URLS]
for task in tasks:
url, status = task.result()
print(f"{status} — {url}")
if __name__ == "__main__":
asyncio.run(main())Ограничение параллелизма семафором:
sem = asyncio.Semaphore(5)
async def fetch_limited(client: httpx.AsyncClient, url: str):
async with sem:
return await fetch(client, url)Таймаут на группу операций:
async with asyncio.timeout(5):
await long_operation()Почему: TaskGroup безопаснее asyncio.gather — при ошибке в одной задаче остальные отменяются, а все исключения собираются в ExceptionGroup.
Плохо:
import os
path = os.path.join(os.getcwd(), "data", "report.txt")
f = open(path)
text = f.read()
f.close()Хорошо:
from pathlib import Path
path = Path.cwd() / "data" / "report.txt"
text = path.read_text(encoding="utf-8")
# все CSV-файлы рекурсивно
for csv_file in Path("data").rglob("*.csv"):
print(csv_file.name, csv_file.stat().st_size)
# создать папку, если её нет
Path("output").mkdir(parents=True, exist_ok=True)Почему: pathlib кроссплатформенный, читаемый и сам закрывает файлы. Всегда указывайте encoding="utf-8".
class ValidationError(Exception):
"""Ошибка валидации пользовательских данных."""
def parse_age(raw: str) -> int:
try:
age = int(raw)
except ValueError as exc:
raise ValidationError(f"Возраст должен быть числом, получено: {raw!r}") from exc
if not 0 <= age <= 150:
raise ValidationError(f"Недопустимый возраст: {age}")
return ageОбработка нескольких ошибок сразу через except*:
try:
raise ExceptionGroup("ошибки", [ValueError("a"), TypeError("b")])
except* ValueError as group:
print("ValueError:", group.exceptions)
except* TypeError as group:
print("TypeError:", group.exceptions)Правила:
- никогда не пишите голый
except:— он глотает дажеKeyboardInterrupt; - ловите конкретные исключения;
- используйте
raise ... from exc, чтобы сохранить причину; - создавайте свои классы исключений для бизнес-логики.
Контекстный менеджер для замера времени:
import time
from contextlib import contextmanager
@contextmanager
def timer(label: str):
start = time.perf_counter()
try:
yield
finally:
print(f"{label}: {time.perf_counter() - start:.3f} с")
with timer("Сортировка"):
sorted(range(1_000_000), reverse=True)Декоратор с повторными попытками (retry):
import functools
import time
def retry(times: int = 3, delay: float = 1.0, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, times + 1):
try:
return func(*args, **kwargs)
except exceptions as exc:
if attempt == times:
raise
print(f"Попытка {attempt} не удалась: {exc}. Повтор через {delay} с")
time.sleep(delay)
return wrapper
return decorator
@retry(times=3, delay=0.5, exceptions=(ConnectionError,))
def unstable_request():
...Кэширование результатов:
from functools import cache
@cache
def fib(n: int) -> int:
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(100))Генераторы обрабатывают большие данные без загрузки всего в память:
from pathlib import Path
def read_large_file(path: Path):
with path.open(encoding="utf-8") as f:
for line in f:
yield line.rstrip("\n")
errors = (line for line in read_large_file(Path("app.log")) if "ERROR" in line)
for line in errors:
print(line)Полезные функции itertools:
from itertools import batched, chain, groupby, islice, pairwise
list(batched(range(10), 3)) # [(0,1,2), (3,4,5), (6,7,8), (9,)] — Python 3.12+
list(pairwise([1, 2, 3, 4])) # [(1,2), (2,3), (3,4)]
list(chain([1, 2], [3], [4, 5])) # [1, 2, 3, 4, 5]
list(islice(range(100), 5)) # [0, 1, 2, 3, 4]
data = sorted(["apple", "avocado", "banana", "cherry"], key=lambda s: s[0])
for letter, group in groupby(data, key=lambda s: s[0]):
print(letter, list(group))import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)-8s | %(name)s | %(message)s",
)
logger = logging.getLogger(__name__)
def process(order_id: int) -> None:
logger.info("Обработка заказа %s", order_id)
try:
...
except Exception:
logger.exception("Ошибка при обработке заказа %s", order_id)
raiseПочему: уровни логов, время, имя модуля и трассировка ошибок — всё это print не умеет. Передавайте аргументы через %s, а не f-строкой: форматирование выполнится, только если сообщение действительно будет выведено.
Полный рабочий код каждой темы лежит в папке advanced/, а тесты — в tests/test_advanced.py. Все примеры проверены на Python 3.12, 3.13 и 3.14 (CI в GitHub Actions). Задачи уровня senior с решениями — в разделе «Практика».
git clone https://github.com/justxor/python-primery-koda-2026.git
cd python-primery-koda-2026
uv run python -m advanced.asyncio_patterns # запустить любой пример
uv run --with pytest pytest # прогнать все тесты| # | Тема | Файл | Уровень |
|---|---|---|---|
| 11 | Дескрипторы, __set_name__, ленивые атрибуты |
descriptors.py |
middle+ |
| 12 | __init_subclass__, метаклассы, type() |
metaprogramming.py |
senior |
| 13 | PEP 695, Protocol, ParamSpec, overload, TypeIs |
typing_advanced.py |
middle+ |
| 14 | Semaphore, Queue, retry, timeout, shield |
asyncio_patterns.py |
middle+ |
| 15 | GIL, free-threading, InterpreterPoolExecutor |
concurrency.py |
senior |
| 16 | contextvars и request_id в логах |
contextvars_demo.py |
middle+ |
| 17 | __slots__, weakref, tracemalloc, cProfile |
performance.py |
middle+ |
| 18 | send(), yield from, конвейеры, ExitStack |
generators_advanced.py |
middle+ |
| 19 | Result, DI, singledispatch, Circuit Breaker, EventBus |
patterns.py |
senior |
| 20 | t-строки, annotationlib, except A, B |
python314.py |
все |
property, classmethod, staticmethod и даже обычные методы — это дескрипторы. Свой дескриптор позволяет один раз описать правило и переиспользовать его в любом классе:
class Positive:
def __set_name__(self, owner, name): # Python сам сообщит имя атрибута
self.name, self.private = name, f"_{name}"
def __get__(self, instance, owner=None):
return self if instance is None else getattr(instance, self.private)
def __set__(self, instance, value):
if not isinstance(value, (int, float)) or value <= 0:
raise ValueError(f"{self.name} должно быть > 0, получено {value!r}")
setattr(instance, self.private, value)
class Product:
price = Positive()
quantity = Positive()
def __init__(self, price, quantity):
self.price = price # вызывает Positive.__set__
self.quantity = quantity
Product(100, 2)
Product(-1, 2) # ValueError: price должно быть > 0, получено -1Важно: дескриптор с __set__ (data descriptor) имеет приоритет над __dict__ экземпляра, а без __set__ — нет. На этом построен functools.cached_property: после первого вызова значение кладётся в __dict__ и дескриптор больше не вызывается.
В 90% случаев вместо метакласса хватает __init_subclass__ — например, для автоматического реестра плагинов:
class Exporter:
registry: dict[str, type["Exporter"]] = {}
def __init_subclass__(cls, /, fmt: str, **kwargs):
super().__init_subclass__(**kwargs)
Exporter.registry[fmt] = cls
class JsonExporter(Exporter, fmt="json"): ...
class CsvExporter(Exporter, fmt="csv"): ...
print(Exporter.registry) # {'json': <class JsonExporter>, 'csv': <class CsvExporter>}Метакласс нужен, когда надо вмешаться в сам процесс создания экземпляров или класса — например, синглтон:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Settings(metaclass=SingletonMeta): ...
assert Settings() is Settings()Порядок выбора: декоратор класса → __init_subclass__ → __set_name__ → метакласс.
from collections.abc import Callable
from typing import Protocol, Self
import functools
type JSON = dict[str, JSON] | list[JSON] | str | int | float | bool | None # рекурсивный алиас
class Stack[T]: # дженерик-класс без TypeVar
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> Self: # Self — для цепочек вызовов
self._items.append(item)
return self
class Comparable(Protocol): # структурная типизация
def __lt__(self, other: Self, /) -> bool: ...
def max_item[C: Comparable](items: list[C]) -> C:
return max(items)
def logged[**P, R](func: Callable[P, R]) -> Callable[P, R]: # ParamSpec
@functools.wraps(func)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
print("вызов", func.__name__)
return func(*args, **kwargs)
return wrapperБлагодаря ParamSpec IDE и mypy видят настоящую сигнатуру задекорированной функции, а не (*args, **kwargs). TypeIs (Python 3.13) позволяет писать собственные функции сужения типа, которые работают в обе стороны — в if и в else.
Ограничение параллелизма — не больше N запросов одновременно:
import asyncio
async def gather_limited(coros, limit: int):
sem = asyncio.Semaphore(limit)
async def run(coro):
async with sem:
return await coro
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(run(c)) for c in coros]
return [t.result() for t in tasks]Повтор с экспоненциальной задержкой и джиттером (чтобы тысяча клиентов не повторяла запрос одновременно):
import random
async def retry_async(func, *, attempts=5, base_delay=0.1, max_delay=5.0):
for attempt in range(attempts):
try:
return await func()
except (ConnectionError, TimeoutError):
if attempt == attempts - 1:
raise
await asyncio.sleep(random.uniform(0, min(max_delay, base_delay * 2**attempt)))Ещё в файле: producer/consumer на asyncio.Queue с backpressure, asyncio.timeout(), асинхронные генераторы с корректным закрытием, asyncio.shield() для операций, которые нельзя прерывать, и asyncio.to_thread() для блокирующего кода.
| Задача | Инструмент |
|---|---|
| I/O: сеть, диск, БД | asyncio, ThreadPoolExecutor |
| CPU: вычисления | ProcessPoolExecutor |
| CPU в free-threaded сборке (3.13t / 3.14t) | обычные потоки — GIL выключен |
| CPU с изоляцией, но дешевле процессов (3.14+) | InterpreterPoolExecutor |
import sys, sysconfig
import concurrent.futures as cf
print("free-threaded:", bool(sysconfig.get_config_var("Py_GIL_DISABLED")))
print("GIL включён:", sys._is_gil_enabled()) # Python 3.13+
def best_executor() -> type[cf.Executor]:
if not sys._is_gil_enabled():
return cf.ThreadPoolExecutor
if hasattr(cf, "InterpreterPoolExecutor"): # Python 3.14+
return cf.InterpreterPoolExecutor
return cf.ProcessPoolExecutorПодвох: без GIL гонки данных становятся реальностью. counter += 1 не атомарна — защищайте общее состояние через threading.Lock.
Глобальные переменные и threading.local ломаются в asyncio: в одном потоке одновременно выполняются сотни запросов. ContextVar хранит значение отдельно для каждой задачи:
from contextvars import ContextVar
import logging
request_id: ContextVar[str] = ContextVar("request_id", default="-")
class RequestIdFilter(logging.Filter):
def filter(self, record):
record.request_id = request_id.get() # id запроса в каждой строке лога
return True
async def handle(rid: str):
token = request_id.set(rid)
try:
await do_work() # глубоко внутри request_id.get() вернёт rid
finally:
request_id.reset(token)Так работают middleware в FastAPI/Starlette, OpenTelemetry и structlog.
from dataclasses import dataclass
@dataclass(slots=True, frozen=True)
class Point:
x: float
y: float
# экземпляр без __dict__: ~48 байт вместо ~340, доступ к атрибутам быстрееfunctools.cached_property— вычислить один раз и запомнить на экземпляре.functools.lru_cache— мемоизация чистых функций,cache_info()показывает эффективность.weakref.WeakValueDictionary— кэш, который не мешает сборщику мусора удалять объекты.tracemalloc— где и сколько памяти выделено;cProfile+pstats— где тратится время.timeit— честное сравнение двух вариантов кода.
python -m cProfile -s cumulative script.py | head -20
python -X importtime -c "import mymodule" # что медленно импортируетсяПравило: сначала измерьте, потом оптимизируйте.
def running_average():
total = count = 0
average = 0.0
while True:
value = yield average # получаем значение через send()
total += value
count += 1
average = total / count
avg = running_average()
next(avg) # «прогрев» до первого yield
avg.send(10); avg.send(20) # 15.0Конвейер генераторов обрабатывает лог в десятки гигабайт с постоянным потреблением памяти:
errors = count_by("service", only("ERROR", parse(open("app.log"))))ExitStack — когда количество контекстных менеджеров заранее неизвестно:
from contextlib import ExitStack
with ExitStack() as stack:
files = [stack.enter_context(open(p, encoding="utf-8")) for p in paths]
... # все файлы гарантированно закроютсяОшибки как значения + match/case:
from dataclasses import dataclass
@dataclass(frozen=True)
class Ok[T]:
value: T
@dataclass(frozen=True)
class Err[E]:
error: E
type Result[T, E] = Ok[T] | Err[E]
def safe_divide(a: float, b: float) -> Result[float, str]:
return Err("деление на ноль") if b == 0 else Ok(a / b)
match safe_divide(1, 0):
case Ok(value):
print("результат", value)
case Err(error):
print("ошибка", error)Внедрение зависимостей через Protocol: бизнес-логика зависит от интерфейса UserRepository, а не от PostgreSQL. В тестах подставляется InMemoryUserRepo — без моков и patch.
Circuit Breaker — после N ошибок подряд перестаём обращаться к упавшему сервису на время reset_timeout, затем пробуем снова (состояния closed → open → half-open). Полная реализация и тесты с подменой часов — в patterns.py.
t-строки (PEP 750) выглядят как f-строки, но возвращают объект Template, а не готовую строку. Это позволяет безопасно обработать подстановки — например, экранировать HTML или превратить запрос в параметризованный SQL:
from string.templatelib import Interpolation, Template
def sql(template: Template) -> tuple[str, list]:
query, params = [], []
for part in template:
if isinstance(part, Interpolation):
query.append("?")
params.append(part.value)
else:
query.append(part)
return "".join(query), params
name = "Robert'); DROP TABLE students;--"
sql(t"SELECT * FROM users WHERE name = {name}")
# ('SELECT * FROM users WHERE name = ?', ["Robert'); DROP TABLE students;--"])Отложенные аннотации (PEP 649): можно ссылаться на класс, объявленный ниже, без кавычек и from __future__ import annotations; читать аннотации — через annotationlib.get_annotations().
PEP 758: except ValueError, TypeError: — скобки больше не обязательны (если нет as).
Python 3.13:
- новый интерактивный REPL с подсветкой и многострочным редактированием;
- экспериментальная сборка без GIL (free-threaded, PEP 703);
- экспериментальный JIT-компилятор (PEP 744);
- более понятные и цветные сообщения об ошибках.
Python 3.14:
- t-строки (template strings, PEP 750) — шаблоны для безопасной подстановки, например в SQL и HTML;
- отложенное вычисление аннотаций (PEP 649/749) и модуль
annotationlib; exceptиexcept*без скобок при перечислении нескольких исключений (PEP 758);- несколько интерпретаторов в одном процессе —
concurrent.interpreters(PEP 734); - модуль
compression.zstdдля сжатия Zstandard (PEP 784); - free-threaded сборка получила официальную поддержку (PEP 779);
InterpreterPoolExecutorвconcurrent.futures— пул на субинтерпретаторах.
Пример t-строки (Python 3.14):
from string.templatelib import Template
name = "<script>alert(1)</script>"
template: Template = t"Привет, {name}!"
print(template.strings) # ('Привет, ', '!')
print([i.value for i in template.interpolations]) # ['<script>alert(1)</script>']Пример except без скобок (Python 3.14):
try:
connect()
except TimeoutError, ConnectionRefusedError:
print("Сервер недоступен")Python 3.15 (релиз запланирован на 1 октября 2026 года, PEP 790):
- ленивые импорты
lazy import(PEP 810) — модуль загружается при первом обращении, программы стартуют быстрее; - распаковка в генераторах списков
[*L for L in lists](PEP 798); - встроенные типы
frozendict(PEP 814) иsentinel(PEP 661); - UTF-8 по умолчанию для
open()и других функций ввода-вывода (PEP 686); - новый пакет
profilingс сэмплирующим профайлером Tachyon (PEP 799); - в типизации:
TypedDictс дополнительными ключами (PEP 728),TypeForm(PEP 747); - заметно доработанный экспериментальный JIT-компилятор.
Примеры Python 3.15:
lazy import json # json загрузится только при первом использовании
lazy from pathlib import Path
lists = [[1, 2], [3, 4], [5]]
print([*L for L in lists]) # [1, 2, 3, 4, 5]
dicts = [{"a": 1}, {"b": 2}, {"a": 3}]
print({**d for d in dicts}) # {'a': 3, 'b': 2}
config = frozendict(host="localhost", port=8080) # неизменяемый и хешируемый
cache = {config: "подключение"} # можно использовать как ключ
MISSING = sentinel("MISSING") # вместо object() — с понятным repr
def get(key: str, default=MISSING):
if default is MISSING:
...Профилирование работающего процесса без перезапуска:
python -m profiling.sampling attach 12345
python -m profiling.sampling run --flamegraph script.pyПопробуйте решить задачу самостоятельно, а затем откройте решение.
Напишите функцию, которая проверяет, является ли строка палиндромом без учёта регистра, пробелов и знаков препинания.
Решение
def is_palindrome(text: str) -> bool:
cleaned = [ch.lower() for ch in text if ch.isalnum()]
return cleaned == cleaned[::-1]
assert is_palindrome("А роза упала на лапу Азора")
assert not is_palindrome("Python")Найдите 5 самых частых слов в тексте.
Решение
import re
from collections import Counter
def top_words(text: str, n: int = 5) -> list[tuple[str, int]]:
words = re.findall(r"\w+", text.lower())
return Counter(words).most_common(n)Сгруппируйте слова-анаграммы: ["кот", "ток", "сон", "нос", "кит"].
Решение
from collections import defaultdict
def group_anagrams(words: list[str]) -> list[list[str]]:
groups: defaultdict[str, list[str]] = defaultdict(list)
for word in words:
groups["".join(sorted(word))].append(word)
return list(groups.values())
print(group_anagrams(["кот", "ток", "сон", "нос", "кит"]))
# [['кот', 'ток'], ['сон', 'нос'], ['кит']]Проверьте, правильно ли расставлены скобки ()[]{} в строке.
Решение
def is_balanced(s: str) -> bool:
pairs = {")": "(", "]": "[", "}": "{"}
stack: list[str] = []
for ch in s:
if ch in "([{":
stack.append(ch)
elif ch in pairs:
if not stack or stack.pop() != pairs[ch]:
return False
return not stack
assert is_balanced("{[()()]}")
assert not is_balanced("([)]")Реализуйте кэш фиксированного размера, вытесняющий давно неиспользуемые элементы.
Решение
from collections import OrderedDict
class LRUCache[K, V]:
def __init__(self, capacity: int) -> None:
self.capacity = capacity
self._data: OrderedDict[K, V] = OrderedDict()
def get(self, key: K) -> V | None:
if key not in self._data:
return None
self._data.move_to_end(key)
return self._data[key]
def put(self, key: K, value: V) -> None:
self._data[key] = value
self._data.move_to_end(key)
if len(self._data) > self.capacity:
self._data.popitem(last=False)
cache = LRUCache[str, int](2)
cache.put("a", 1)
cache.put("b", 2)
cache.get("a")
cache.put("c", 3) # вытесняет "b"
assert cache.get("b") is NoneВыполните 20 асинхронных задач, но не более 5 одновременно.
Решение
import asyncio
import random
async def job(i: int, sem: asyncio.Semaphore) -> int:
async with sem:
await asyncio.sleep(random.uniform(0.1, 0.5))
return i * i
async def main() -> None:
sem = asyncio.Semaphore(5)
async with asyncio.TaskGroup() as tg:
tasks = [tg.create_task(job(i, sem)) for i in range(20)]
print([t.result() for t in tasks])
asyncio.run(main())Превратите произвольно вложенный список в плоский: [1, [2, [3, [4]], 5]] → [1, 2, 3, 4, 5].
Решение
from collections.abc import Iterable, Iterator
def flatten(items: Iterable) -> Iterator:
for item in items:
if isinstance(item, Iterable) and not isinstance(item, (str, bytes)):
yield from flatten(item)
else:
yield item
assert list(flatten([1, [2, [3, [4]], 5]])) == [1, 2, 3, 4, 5]Эти задачи часто дают на собеседованиях и в тестовых заданиях. Полные решения с типами и тесты лежат в advanced/tasks_senior.py и tests/test_tasks_senior.py.
Напишите декоратор @ttl_cache(ttl=60, maxsize=128): результат функции живёт ttl секунд, а при переполнении вытесняется самая старая запись. Время должно быть подменяемым, чтобы кэш можно было тестировать без sleep.
Решение
import functools
import time
from collections import OrderedDict
def ttl_cache(ttl: float, maxsize: int = 128, clock=time.monotonic):
def decorator(func):
data: OrderedDict = OrderedDict()
@functools.wraps(func)
def wrapper(*args, **kwargs):
key = (args, tuple(sorted(kwargs.items())))
now = clock()
if key in data:
expires, value = data[key]
if expires > now:
data.move_to_end(key)
return value
del data[key] # запись протухла
value = func(*args, **kwargs)
data[key] = (now + ttl, value)
if len(data) > maxsize:
data.popitem(last=False) # вытесняем самую старую
return value
return wrapper
return decoratorПочему time.monotonic, а не time.time: системные часы могут перевести назад, а монотонные — нет.
Сотня корутин одновременно вызывает await loader.load(user_id). Нужно склеить их в один запрос fetch_users([ids]), убрать дубликаты и раздать каждому его результат.
Решение
import asyncio
class Batcher:
def __init__(self, batch_fn, delay: float = 0.005) -> None:
self.batch_fn = batch_fn
self.delay = delay
self._pending: dict = {}
self._timer = None
def load(self, key) -> asyncio.Future:
if key in self._pending: # дедупликация
return self._pending[key]
loop = asyncio.get_running_loop()
fut = self._pending[key] = loop.create_future()
if self._timer is None: # копим ключи несколько мс
self._timer = loop.call_later(self.delay, self._flush)
return fut
def _flush(self) -> None:
self._timer = None
pending, self._pending = self._pending, {}
asyncio.get_running_loop().create_task(self._dispatch(pending))
async def _dispatch(self, pending: dict) -> None:
try:
result = await self.batch_fn(list(pending))
except Exception as exc:
for fut in pending.values():
fut.set_exception(exc)
return
for key, fut in pending.items():
if key in result:
fut.set_result(result[key])
else:
fut.set_exception(KeyError(key))
async def fetch_users(ids: list[int]) -> dict[int, str]:
print("SQL: WHERE id IN", ids) # один запрос вместо пяти
return {i: f"user{i}" for i in ids}
async def main() -> None:
loader = Batcher(fetch_users)
print(await asyncio.gather(*(loader.load(i) for i in [1, 2, 3, 2, 1])))
asyncio.run(main())Так устроен DataLoader в GraphQL-серверах. В полной версии есть ещё лимит max_batch.
Дан словарь {"app": ["web", "db"], "web": ["http"], ...}. Верните порядок установки, в котором каждый пакет идёт после своих зависимостей, а при цикле выбросьте понятную ошибку.
Решение
from collections import defaultdict, deque
def install_order(deps: dict[str, list[str]]) -> list[str]:
graph = defaultdict(list)
indegree: dict[str, int] = {}
for node, requires in deps.items():
indegree.setdefault(node, 0)
for req in requires:
indegree.setdefault(req, 0)
graph[req].append(node)
indegree[node] += 1
queue = deque(n for n, d in indegree.items() if d == 0)
order = []
while queue:
node = queue.popleft()
order.append(node)
for nxt in graph[node]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
if len(order) != len(indegree):
raise ValueError("Циклическая зависимость")
return order
print(install_order({"app": ["web", "db"], "web": ["http"], "db": ["driver"]}))
# ['http', 'driver', 'web', 'db', 'app']Это алгоритм Кана, O(V + E). В стандартной библиотеке есть готовый graphlib.TopologicalSorter, но на собеседовании обычно просят написать самому.
Реализуйте структуру, которая по префиксу "py" возвращает N самых частых слов: python, pytest, …
Решение
import heapq
class Trie:
__slots__ = ("children", "count")
def __init__(self) -> None:
self.children: dict[str, "Trie"] = {}
self.count = 0
def add(self, word: str, times: int = 1) -> None:
node = self
for ch in word:
node = node.children.setdefault(ch, Trie())
node.count += times
def suggest(self, prefix: str, limit: int = 5) -> list[str]:
node = self
for ch in prefix:
if ch not in node.children:
return []
node = node.children[ch]
found, stack = [], [(node, prefix)]
while stack: # обход без рекурсии
cur, word = stack.pop()
if cur.count:
found.append((-cur.count, word))
stack.extend((child, word + ch) for ch, child in cur.children.items())
return [w for _, w in heapq.nsmallest(limit, found)]
trie = Trie()
for word, freq in {"python": 10, "pytest": 7, "pydantic": 5, "pandas": 8}.items():
trie.add(word, freq)
print(trie.suggest("py", 2)) # ['python', 'pytest']Ключи раскладываются по серверам кэша через hash(key) % N. При добавлении сервера почти все ключи «переезжают» и кэш обнуляется. Сделайте так, чтобы переезжало только около 1/N ключей.
Решение
import bisect
import hashlib
class HashRing:
def __init__(self, nodes=(), replicas: int = 100) -> None:
self.replicas = replicas
self._ring: list[tuple[int, str]] = []
for node in nodes:
self.add(node)
@staticmethod
def _hash(value: str) -> int: # hash() для str меняется между запусками!
return int.from_bytes(hashlib.blake2b(value.encode(), digest_size=8).digest())
def add(self, node: str) -> None:
for i in range(self.replicas): # виртуальные узлы сглаживают нагрузку
bisect.insort(self._ring, (self._hash(f"{node}#{i}"), node))
def remove(self, node: str) -> None:
self._ring = [item for item in self._ring if item[1] != node]
def get(self, key: str) -> str:
idx = bisect.bisect(self._ring, (self._hash(key), ""))
return self._ring[idx % len(self._ring)][1]
ring = HashRing(["cache-1", "cache-2", "cache-3"])
keys = [f"user:{i}" for i in range(1000)]
before = {k: ring.get(k) for k in keys}
ring.add("cache-4")
moved = sum(before[k] != ring.get(k) for k in keys)
print(f"переехало {moved / 10:.1f}% ключей") # ~25%, а не ~75%Есть K отсортированных источников: файлы логов по десятки гигабайт или бесконечные генераторы. Слейте их в один отсортированный поток, не загружая данные в память.
Решение
import heapq
from collections.abc import Iterable, Iterator
def merge_sorted[T](*streams: Iterable[T]) -> Iterator[T]:
heap = []
for idx, stream in enumerate(streams):
it = iter(stream)
for first in it: # берём первый элемент, если он есть
heap.append((first, idx, it))
break
heapq.heapify(heap)
while heap:
value, idx, it = heap[0]
yield value
for nxt in it:
heapq.heapreplace(heap, (nxt, idx, it))
break
else:
heapq.heappop(heap) # поток закончился
print(list(merge_sorted([1, 4, 9], [2, 3, 10], [5]))) # [1, 2, 3, 4, 5, 9, 10]Сложность — O(N log K) по времени и O(K) по памяти. idx в кортеже нужен, чтобы при равных значениях не сравнивать итераторы. В стандартной библиотеке есть heapq.merge, но его устройство спрашивают на собеседованиях.
| Проект | Что изучите | Стек |
|---|---|---|
| Консольный менеджер задач | CLI, файлы, JSON | argparse, pathlib, json |
| Telegram-бот | асинхронность, API | aiogram |
| REST API для заметок | веб, БД, валидация | FastAPI, SQLAlchemy, pydantic |
| Парсер цен | HTTP, HTML, расписание | httpx, selectolax / BeautifulSoup |
| Дашборд по данным | анализ и визуализация | polars, streamlit |
| LLM-ассистент | работа с API нейросетей | SDK провайдера, pydantic |
| ИИ-агент с инструментами | tool calling, MCP, асинхронность | SDK провайдера, MCP Python SDK, httpx |
| Кэширующий прокси | консистентное хеширование, TTL, asyncio | asyncio, httpx, задачи 8 и 12 |
| Очередь фоновых задач | воркеры, retry, graceful shutdown | asyncio, redis, паттерны из advanced/ |
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI(title="Заметки API")
class NoteIn(BaseModel):
title: str
text: str = ""
class Note(NoteIn):
id: int
notes: dict[int, Note] = {}
@app.post("/notes", response_model=Note, status_code=201)
def create_note(data: NoteIn) -> Note:
note = Note(id=len(notes) + 1, **data.model_dump())
notes[note.id] = note
return note
@app.get("/notes/{note_id}", response_model=Note)
def get_note(note_id: int) -> Note:
if note_id not in notes:
raise HTTPException(status_code=404, detail="Заметка не найдена")
return notes[note_id]Запуск: uv add "fastapi[standard]" и uv run fastapi dev main.py, документация — на http://127.0.0.1:8000/docs.
# test_utils.py
import pytest
from utils import is_palindrome, is_balanced
@pytest.mark.parametrize(
("text", "expected"),
[
("шалаш", True),
("А роза упала на лапу Азора", True),
("python", False),
("", True),
],
)
def test_is_palindrome(text: str, expected: bool) -> None:
assert is_palindrome(text) is expected
def test_is_balanced_empty_string() -> None:
assert is_balanced("")
@pytest.fixture
def sample_users() -> list[dict]:
return [{"name": "Анна", "active": True}, {"name": "Борис", "active": False}]
def test_active_users(sample_users: list[dict]) -> None:
active = [u["name"] for u in sample_users if u["active"]]
assert active == ["Анна"]Запуск: uv run pytest -v.
-
Изменяемый аргумент по умолчанию
# плохо def add(item, bucket=[]): bucket.append(item) return bucket # хорошо def add(item, bucket: list | None = None): bucket = [] if bucket is None else bucket bucket.append(item) return bucket
-
Сравнение с
Noneчерез==— используйтеis None. -
Изменение списка во время итерации — итерируйтесь по копии или создавайте новый список.
-
Голый
except:— ловите конкретные исключения. -
Конкатенация строк в цикле — используйте
"".join(parts). -
Отсутствие виртуального окружения — используйте
uvилиvenv. -
Открытие файлов без
withи безencoding="utf-8". -
from module import *— засоряет пространство имён.
- Чем список отличается от кортежа? Что такое изменяемые и неизменяемые типы?
- Как работает GIL и что изменилось в free-threaded Python 3.13/3.14?
- В чём разница между
isи==? - Что такое генератор и чем
yieldотличается отreturn? - Как устроен словарь (
dict) и почему ключ должен быть хешируемым? - Что такое декоратор? Напишите декоратор с параметрами.
- Чем
asyncioотличается отthreadingиmultiprocessing? - Что такое MRO и как работает
super()? - Зачем нужны
__slots__? - Как работают контекстные менеджеры (
__enter__/__exit__)? - Поверхностное и глубокое копирование:
copyvsdeepcopy. - Что такое
*argsи**kwargs?
Продвинутый уровень (middle+ / senior):
- Что такое дескриптор? Чем data descriptor отличается от non-data? Как устроен
property? - Когда нужен метакласс, а когда достаточно
__init_subclass__или декоратора класса? - Как работает
asyncio.TaskGroupи что происходит с остальными задачами, если одна упала? - Как ограничить число одновременных запросов в asyncio? Зачем джиттер в retry?
- Чем
contextvarsотличается отthreading.localи почему это важно для asyncio? - Что даёт free-threaded Python и какие проблемы он создаёт для существующего кода?
- Чем
ProcessPoolExecutorотличается отInterpreterPoolExecutor(3.14)? - Как найти утечку памяти? Что покажут
tracemallocиgc.get_referrers? - Чем t-строки (PEP 750) отличаются от f-строк и зачем они нужны?
- Что такое
ParamSpecи зачем он декораторам? - Как реализовать Circuit Breaker и чем он отличается от retry?
- Что такое проблема N+1 и как её решает батчинг (DataLoader)?
- Зачем нужно консистентное хеширование?
- Что дают ленивые импорты в Python 3.15 и какие у них подводные камни?
- Чем
frozendictотличается отMappingProxyType?
С чего начать изучение Python в 2026 году?
Установите Python 3.14 и uv, изучите базовый синтаксис, типы данных, функции, затем ООП и работу с файлами. После этого решайте задачи из раздела «Практика» и делайте мини-проекты.
Какую версию Python выбрать? Для новых проектов — Python 3.14: это актуальная стабильная версия. Python 3.15 выходит 1 октября 2026 года; переходите на него, когда ваши зависимости начнут его поддерживать. Для рабочих проектов берите ту версию, которую поддерживают ваши библиотеки.
Нужна ли типизация в Python? Не обязательна, но в 2026 году это стандарт в командной разработке: меньше ошибок, лучше автодополнение, проще рефакторинг.
Что лучше: pip или uv?
uv значительно быстрее и объединяет управление версиями Python, окружениями и зависимостями. pip остаётся стандартным и работает везде.
Где практиковаться в Python? Задачи из этого репозитория, LeetCode, Codewars, Stepik, Advent of Code, а также собственные pet-проекты.
С чего начать продвинутые темы?
С раздела «Продвинутые темы»: запустите файлы из папки advanced/, прочитайте тесты и попробуйте изменить код так, чтобы тест упал, — это лучший способ понять, как всё работает.
Как подготовиться к собеседованию Python-разработчика? Повторите базовые структуры данных, генераторы, декораторы, асинхронность, ООП, тестирование, SQL и Git. Решите 30–50 алгоритмических задач и подготовьте 1–2 проекта для портфолио.
- Официальная документация Python
- Что нового в Python 3.14
- Что нового в Python 3.15
- PEP 8 — руководство по стилю кода
- Real Python
- Документация uv
- Документация Ruff
- Тайпчекер ty
- Документация FastAPI
- Документация pytest
- Descriptor HowTo Guide
- PEP 695 — синтаксис параметров типов
- PEP 750 — t-строки
- PEP 703 — Python без GIL
- Руководство по free-threaded Python
python-primery-koda-2026/
├── README.md # руководство: примеры, задачи, вопросы
├── advanced/ # продвинутые темы: код, который можно запустить
│ ├── descriptors.py # 11. дескрипторы
│ ├── metaprogramming.py # 12. __init_subclass__, метаклассы
│ ├── typing_advanced.py # 13. PEP 695, Protocol, ParamSpec
│ ├── asyncio_patterns.py # 14. паттерны asyncio
│ ├── concurrency.py # 15. GIL, free-threading
│ ├── contextvars_demo.py # 16. contextvars
│ ├── performance.py # 17. производительность и память
│ ├── generators_advanced.py # 18. генераторы, ExitStack
│ ├── patterns.py # 19. Result, DI, Circuit Breaker
│ ├── python314.py # 20. t-строки, annotationlib
│ └── tasks_senior.py # решения задач 8–13
├── tests/ # pytest-тесты для всего кода
├── pyproject.toml
└── .github/workflows/tests.yml # CI: Python 3.12, 3.13, 3.14
- Поставьте звезду репозиторию, чтобы его увидело больше разработчиков.
- Предложите свой пример кода или задачу через Pull Request.
- Нашли ошибку — создайте Issue.
Теги: python python3 python-примеры примеры-кода python-2026 лучшие-практики чистый-код задачи-по-python python-для-начинающих asyncio fastapi pytest типизация собеседование-python python-3-14 дескрипторы метаклассы free-threading contextvars паттерны-проектирования python-3-15 задачи-для-senior
Лицензия: MIT