← все записи

Python tricks, которые я использую каждый день

18 ноября 2025 · Alex Volk
Python Backend

Это не список экзотических фишек ради демонстрации знания языка. Всё что ниже — то, что я реально использую в повседневной работе и что реально экономит время.

Walrus operator := (Python 3.8+)

Позволяет присваивать переменную прямо в выражении. Особенно полезно в while-циклах и условиях:

# Старый способ
line = file.readline()
while line:
    process(line)
    line = file.readline()

# С walrus
while line := file.readline():
    process(line)

# В условиях
if m := re.search(r'\d+', text):
    print(f"Found number: {m.group()}")

itertools — мощь без лишнего кода

Модуль, который надо знать наизусть:

from itertools import islice, chain, groupby, batched  # batched — Python 3.12

# Разбить список на чанки (до 3.12)
def chunks(lst, n):
    it = iter(lst)
    while batch := list(islice(it, n)):
        yield batch

# Разбить на чанки (3.12+)
for batch in batched(items, 100):
    process_batch(batch)

# Объединить несколько итерируемых
all_items = list(chain(list1, list2, list3))

# Группировка (требует предварительной сортировки)
data = sorted(records, key=lambda x: x['status'])
for status, group in groupby(data, key=lambda x: x['status']):
    print(f"{status}: {len(list(group))} records")

dataclasses с умом

Dataclasses — это не просто замена namedtuple. Несколько паттернов:

from dataclasses import dataclass, field, asdict
from typing import ClassVar

@dataclass
class Config:
    host: str = "localhost"
    port: int = 8080
    tags: list = field(default_factory=list)  # правильный mutable default
    _instance_count: ClassVar[int] = 0  # не попадёт в __init__

    def __post_init__(self):
        # Валидация при инициализации
        if not 1 <= self.port <= 65535:
            raise ValueError(f"Invalid port: {self.port}")

# Конвертация в dict (рекурсивная)
config = Config(host="db.internal", port=5432)
config_dict = asdict(config)  # {'host': 'db.internal', 'port': 5432, 'tags': []}

contextlib для управления ресурсами

Не всегда нужен полноценный класс с __enter__/__exit__:

from contextlib import contextmanager, suppress, asynccontextmanager
import time

@contextmanager
def timer(name=""):
    start = time.perf_counter()
    try:
        yield
    finally:
        elapsed = time.perf_counter() - start
        print(f"{name}: {elapsed:.3f}s")

with timer("database query"):
    result = db.execute(heavy_query)

# suppress — игнорировать конкретные исключения
with suppress(FileNotFoundError):
    os.unlink("/tmp/tempfile")

# asynccontextmanager для async кода
@asynccontextmanager
async def managed_connection(pool):
    conn = await pool.acquire()
    try:
        yield conn
    finally:
        await pool.release(conn)

functools.cache / lru_cache

from functools import lru_cache, cache

# cache (Python 3.9+) — unbounded cache
@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# lru_cache с ограничением
@lru_cache(maxsize=128)
def get_user_permissions(user_id: int) -> frozenset:
    return frozenset(db.get_permissions(user_id))

# Инвалидация кеша
get_user_permissions.cache_clear()
# Статистика
print(get_user_permissions.cache_info())

Типизация: TypedDict и Protocol

Вместо просто dict — TypedDict для явной документации структуры:

from typing import TypedDict, Protocol, runtime_checkable

class UserData(TypedDict):
    id: int
    name: str
    email: str
    is_active: bool

def process_user(user: UserData) -> None:
    print(f"Processing {user['name']}")

# Protocol — duck typing с проверкой типов
@runtime_checkable
class Serializable(Protocol):
    def to_dict(self) -> dict: ...
    def to_json(self) -> str: ...

def save(obj: Serializable) -> None:
    data = obj.to_dict()
    # ...

Параллелизм с asyncio

Паттерн, который использую для параллельных I/O операций:

import asyncio
from asyncio import TaskGroup  # Python 3.11+

async def fetch_all(urls: list[str]) -> list[dict]:
    async with TaskGroup() as tg:
        tasks = [tg.create_task(fetch(url)) for url in urls]
    return [t.result() for t in tasks]

# Semaphore для ограничения параллелизма
sem = asyncio.Semaphore(10)

async def fetch_limited(url: str):
    async with sem:
        return await fetch(url)

Список можно продолжать долго. Следующая часть будет про декораторы и метаклассы — но это уже когда будет желание объяснять магию.