Среда и инструменты
Единый тулчейн курса:
- uv - окружение и зависимости
- ruff - стиль и ошибки
- mypy - типы
- pre-commit - авто-проверка перед коммитом
Тот же тулчейн гоняет CI - значит «зелёно локально» = «зелёно в грейдере»
Стандарт стиля - PEP 8, но следить за ним вручную не нужно - это работа ruff, а не твоей памяти
Теория
PEP 8 - стандарт стиля
PEP 8 - официальный style guide для Python (Python Enhancement Proposal №8)
Один и тот же код можно оформить десятком способов - без общих правил в командном проекте начинается
разнобой, из-за которого сложнее читать чужой код и легче пропустить баг на ревью
Код читают чаще, чем пишут - и читают его другие (а ещё - ты сам через месяц)
Основные правила:
- 4 пробела на уровень отступа, не таб
snake_caseдля переменных и функций,PascalCaseдля классов,UPPER_CASEдля констант- ограничение длины строки (у нас - 88 символов, см.
pyproject.toml) - импорты вверху файла, сгруппированы: стандартная библиотека -> сторонние пакеты -> локальные модули
- 2 пустые строки между функциями/классами верхнего уровня, 1 - между методами класса
Пример - тот же код до и после (ruff format + ruff check --fix сделают это за тебя автоматически):
# до
import os
def CalculateTotal(a,b , c=10):
result=a+b+c
if result>100:
return True
else:
return False
import sys
# после
import os
import sys
def calculate_total(a, b, c=10):
result = a + b + c
if result > 100:
return True
else:
return False
Разница: имя функции snake_case, а не PascalCase; один пробел вокруг ,/=/>, а не произвольное
количество; отступ везде ровно 4 пробела, а не "на глаз"; импорты - оба вверху файла, а не один в
середине кода; 2 пустые строки перед функцией верхнего уровня
Правил много, все не упомнишь - и не нужно: их проверяет ruff (см. ниже), а не твоя память
Понимать ЗАЧЕМ (консистентность, читаемость) важнее, чем держать в голове каждое конкретное число
uv - окружение и зависимости
Виртуальное окружение - изолированная копия Python со своим набором пакетов, отдельная от
системного Python и от окружений других проектов
Без изоляции: одному проекту нужен django==4.2, другому - django==5.0, и они не могут ужиться в
одном общем (системном) окружении - конфликт версий
Поэтому у каждого проекта - своё окружение, обычно папка .venv внутри его же репозитория
uv - быстрый менеджер окружений и зависимостей (замена python -m venv + pip + poetry/pyenv
разом)
Сам ставит нужную версию Python, создаёт .venv, фиксирует точные версии в uv.lock
uv init- новый проект (создаётpyproject.toml)uv add <пакет>- добавить зависимость (обновитpyproject.toml+uv.lock)uv add --dev <пакет>- dev-зависимость (линтеры/тесты; в прод-образ не попадает)uv sync- поставить окружение РОВНО поuv.lockuv run <cmd>- выполнить команду в окружении проекта (uv run python …,uv run ruff …)
Версия Python фиксируется файлом .python-version (у нас 3.13) и полем requires-python в
pyproject.toml
uv.lock коммитим - это и есть воспроизводимость (CI ставит uv sync --frozen)
ruff - линтер и форматтер
ruff - один инструмент вместо связки flake8 + pylint + isort + black
Делает две разные вещи:
- линт (
ruff check) - находит ошибки и нарушения стиля, многое чинит сам (--fix) - формат (
ruff format) - приводит код к единому виду (аналог black)
uv add --dev "ruff>=0.14,<0.15"
uv run ruff check . # линт
uv run ruff check --fix . # + авто-фиксы
uv run ruff format . # форматирование
uv run ruff check --watch . # следить за файлами и перепроверять на лету
Держим ruff>=0.14,<0.15: в 0.15.x баг форматтера режет скобки в except-кортеже → получается
невалидный Python
Снимем пин, когда апстрим починит
mypy - статическая типизация
Аннотации типов (a: int, def f(x: str) -> bool:) не влияют на выполнение - Python их в рантайме
игнорирует
Их проверяет mypy: находит несоответствия типов ДО запуска
uv add --dev mypy
uv run mypy .
Типизация в Python
Аннотация - это «контракт» функции для других разработчиков и для IDE (автодополнение, подсветка ошибок)
- Встроенные типы
intfloatboolstrlistdictsettupleNone
- Примеры
a: int = 5names: list[str] = []def add(a: int, b: int) -> int: ...
- Модуль
typingдля сложных случаевOptional[X](=X | None)Union(=X | Y)
from typing import Callable, Literal, Any
def apply(fn: Callable[[int, int], int], a: int, b: int) -> int:
return fn(a, b) # fn - функция с сигнатурой (int, int) -> int
def request(method: Literal['GET', 'POST']) -> None: ... # только эти строки, не любой str
def log(value: Any) -> None: # 'любой тип' - mypy не проверяет это место
print(value)
Generic - для своих обобщённых классов (свой list[T]); понадобится не на старте, отдельно
разбирать здесь не будем
Docstrings
Docstring - строка сразу под def/class или в начале модуля, описывающая, что делает объект
Google-style:
def transform(path: str, angle: int) -> bytes:
"""Повернуть изображение на заданный угол.
Args:
path: путь к файлу изображения.
angle: угол поворота в градусах.
Returns:
Байты повёрнутого изображения.
"""
Расширение autoDocstring генерирует каркас по сигнатуре
В нашем ruff-конфиге правило D (pydocstyle) не включено - docstrings это хороший тон, но не жёсткое
требование грейдера
pyproject.toml - единый конфиг
Весь тулинг настраивается в одном файле
Конфиг курса:
[tool.ruff]
line-length = 88
indent-width = 4
target-version = "py313"
[tool.ruff.lint]
fixable = ["ALL"]
select = ["E", "F", "FAST", "PL", "I", "ARG", "RET", "ASYNC", "B", "A", "ICN", "SLF", "SIM", "C90", "N", "W"]
[tool.ruff.lint.mccabe]
max-complexity = 10
[tool.ruff.format]
quote-style = "single"
indent-style = "space"
[tool.mypy]
plugins = ["pydantic.mypy"]
select - включённые семейства правил: E/W - PEP 8, F - ошибки, I - сортировка импортов,
N - имена, B - багбир (типичные баги), PL - pylint, SIM - упрощения, C90 - цикломатическая
сложность (см. ниже) и т.д.
quote-style = "single" - строковые литералы одинарными кавычками (наш стиль; docstrings ruff
оставляет тройными двойными """)
Цикломатическая сложность (C90)
Грубо говоря - число независимых путей выполнения через функцию: считается по числу ветвлений
(if/elif/for/while/except/and/or) плюс один за саму функцию
Чем больше ветвлений в одной функции - тем больше сценариев нужно держать в голове и покрыть
тестами
Поэтому на неё стоит порог (max-complexity) - ruff выдаёт C901 ... is too complex, если функция
его превысила, и это сигнал разбить её на более мелкие, а не бороться с предупреждением
pre-commit - проверки до коммита
pre-commit вешает проверки на git commit: код автоматически линтится и форматируется ПЕРЕД
попаданием в историю - не забудешь прогнать ruff вручную
.pre-commit-config.yaml:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.14 # серия 0.14.x - совместимо с пином ruff<0.15
hooks:
- id: ruff-check
args: [--fix]
- id: ruff-format
uvx pre-commit install # поставить хук в репозиторий (один раз)
IDE (VS Code)
Расширения: Python, Pylance (автодополнение/подсветка), Ruff, Mypy Type Checker,
autoDocstring
Тема - на вкус (напр. Dark+)
Тогда ruff/mypy работают прямо в редакторе: подсветка проблем и формат при сохранении / из
контекстного меню
Примеры
Поднять проект с нуля и настроить тулинг:
uv init myproject && cd myproject
uv add --dev "ruff>=0.14,<0.15" mypy
# вставить секции ruff/mypy в pyproject.toml (см. выше)
uv run ruff format .
uv run ruff check --fix .
uv run mypy .
Типизированная функция с docstring и строковым литералом:
def greet(name: str) -> str:
"""Собрать приветствие для пользователя."""
return f'Привет, {name}!' # строки - одинарные кавычки (quote-style = 'single')
Проверь себя
1. Что такое PEP 8 и кто на практике следит за его соблюдением в коде?
PEP 8 - официальный style guide Python (отступы, именование, длина строки и т.д.)
На практике правила проверяет ruff - держать весь стандарт в памяти не нужно
2. Чем ruff check отличается от ruff format?
check - линтер (находит ошибки/нарушения, часть чинит с --fix); format - форматтер (единый
вид кода)
Разные задачи, ruff умеет обе
3. Зачем коммитить uv.lock?
В нём зафиксированы точные версии всех зависимостей → у всех и в CI ставится ровно одно и то же
окружение (воспроизводимость)
CI ставит uv sync --frozen строго по локу
4. Проверяет ли Python аннотации типов при запуске?
Нет
В рантайме аннотации игнорируются; их проверяет отдельный инструмент - mypy - ДО запуска
5. Что делает pre-commit?
Вешает проверки (линт/формат) на git commit - код проверяется автоматически перед попаданием в
историю
6. Где настраивается ruff и mypy?
В pyproject.toml: секции [tool.ruff], [tool.ruff.lint], [tool.ruff.format], [tool.mypy]
7. Какой командой добавить dev-зависимость через uv?
uv add --dev <пакет> (напр. uv add --dev ruff mypy)
8. Что измеряет цикломатическая сложность (C90) и что делать при C901 ... is too complex?
Грубо - число независимых путей выполнения через функцию (ветвления if/for/while/except
и т.д.)
Предупреждение значит, что функция взяла на себя слишком много веток - обычно её стоит разбить на
более мелкие, а не подавлять предупреждение
Итог / чек-лист
- PEP 8 - стандарт стиля Python; правила проверяет
ruff, а не память - Окружение и зависимости - только через
uv(uv add/uv sync/uv run); Python пинится.python-version(3.13);uv.lockкоммитим ruff= линт + формат;mypy= типы; конфиг - вpyproject.toml(единый на курс)- Цикломатическая сложность (
C90/C901, mccabe) - метрика ветвистости функции; предупреждение - сигнал разбить функцию, а не игнорировать pre-commitгоняет ruff перед коммитом; тот же тулчейн - в CI-грейдере- Аннотации типов + docstrings - читаемость и контракт функций
Дополнительно
- Хэндбук Яндекса - Python
- Документация: uv · ruff · mypy