Опирается на правила: PY-3.1, PY-3.2, PY-3.3, PY-3.4, PY-RUFF-1, PY-RUFF-3, PY-RUFF-4 из Python Style Guide → раздел 3. Импорты.

Важно знать

  • Только абсолютные импортыfrom ..core.order import Order запрещён на любой глубине пакета.
  • Wildcard запрещёнfrom app.core.order import * ломает mypy и делает неизвестным происхождение имён.
  • Неиспользуемые импорты удаляются автоматически при ruff check --fix (правило F401).
  • Порядок групп: stdlib → third-party → local; пустая строка между группами; ruff (isort-совместимый) поддерживает его механически — руками не расставлять.
  • Стиль импорта: import module для модулей целиком; from module import name для конкретных имён; не смешивать ради «короче».
  • ruff покрывает весь раздел механически — F401 (unused), F403 (wildcard), I001 (порядок isort); в findings ревью дублировать не нужно.
  • Конфигурация ruff — в pyproject.toml, раздел [tool.ruff]; отдельные .isort.cfg/.flake8 не используются.
  • # noqa: F401 без обоснования запрещён — если импорт нужен только для side-effect, добавить # noqa: F401 # justify: ....

Структура импортов — первый сигнал о связности модуля. Relative import from ..order import Order скрывает реальный путь к символу и ломается при переносе пакета. Wildcard делает невидимым, откуда пришло каждое имя — mypy отказывается анализировать такой код в strict-режиме. Ruff устраняет всю механику: группировку, сортировку, поиск unused — за CI.

Только абсолютные импорты

PY-3.1: путь всегда от корня пакета.

from app.core.order import Order
from app.customer.repository import CustomerRepository
import logging
from ..core.order import Order
from .repository import CustomerRepository

Relative-импорт кажется удобным внутри одного пакета — from . import utils короче. Но он привязывает файл к своей позиции в иерархии: стоит переместить order/ в domains/order/ — все from .. сломаются. Абсолютный путь работает независимо от расположения файла, IDE навигирует по нему напрямую, а ruff может автоматически исправить порядок.

Без wildcard

PY-3.2: from module import * не допускается.

from app.core.order import Order, OrderStatus, OrderId
from app.core.order import *

Wildcard-импорт переносит в пространство имён модуля всё, что экспортирует источник. Последствия:

  • mypy в --strict не может отследить тип Order, пришедшего через * — проверка типов перестаёт работать.
  • При конфликте имён (Order из двух пакетов) Python молча выбирает последний wildcard — ошибку не увидите до runtime.
  • Рефакторинг — переименовать OrderStatus в OrderState в источнике — не найдёт использований через *.

Ruff ловит F403 на CI и блокирует merge.

Порядок групп: ruff делает это за вас

PY-3.3: три группы, пустая строка между ними.

import logging
import uuid
from datetime import datetime
from decimal import Decimal

from fastapi import Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.order import Order, OrderStatus
from app.customer.repository import CustomerRepository

Группы:

  1. stdlib — стандартная библиотека Python (logging, uuid, datetime, decimal, pathlib).
  2. third-party — пакеты из pip/pyproject.toml (fastapi, pydantic, sqlalchemy, structlog).
  3. local — внутренние модули проекта (app.*, src.*).

ruff (isort-совместимый) расставляет группы и сортирует внутри при ruff check --fix. Конфигурация в pyproject.toml:

[tool.ruff]
line-length = 88

[tool.ruff.lint]
select = ["E", "F", "I", "N", "W"]

[tool.ruff.lint.isort]
known-first-party = ["app"]

Поле known-first-party говорит ruff, что app.* — локальный пакет, а не third-party. Без него все from app. попадут в группу third-party и порядок сломается.

Ручная расстановка порядка — потеря времени и источник конфликтов в merge при добавлении нового импорта. Делегировать ruff и не трогать руками.

Стиль импорта: модуль vs конкретное имя

PY-3.4: import module для модулей целиком; from module import name для конкретных имён.

import logging

from app.core.order import Order
from decimal import Decimal
from logging import getLogger, warning, error, debug, info
import app.core.order

Правило разделяет два случая:

import module — когда модуль используется как пространство имён и вызов выглядит как logging.info(...). Это особенно важно для стандартной библиотеки: logging.getLogger(__name__) сразу сообщает, откуда функция, без поиска по импортам.

from module import name — когда конкретные имена используются напрямую: Order, Decimal, AsyncSession. Длинный путь app.core.order.Order(...) каждый раз — избыточен.

Смешанный стиль ради краткости (from logging import * или import app.core.order с доступом через app.core.order.Order) запрещён — он затрудняет поиск по кодовой базе и делает неоднозначным происхождение имён.

Пример из сервиса OrderService:

import logging

from decimal import Decimal
from uuid import UUID

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.order import Order, OrderStatus
from app.customer.repository import CustomerRepository

logger = logging.getLogger(__name__)


class CreateOrderHandler:
    def __init__(self, session: AsyncSession, customers: CustomerRepository) -> None:
        self._session = session
        self._customers = customers

    async def execute(self, customer_id: UUID, amount: Decimal) -> Order:
        customer = await self._customers.find_by_id(customer_id)
        if customer is None:
            raise ValueError(f"customer {customer_id} not found")
        order = Order.create(customer_id=customer_id, amount=amount)
        self._session.add(order)
        return order

Что запрещено

АнтипаттернПравилоЧто взамен
from ..core.order import OrderPY-3.1from app.core.order import Order
from app.core.order import *PY-3.2явный список: from app.core.order import Order, OrderStatus
Неиспользуемый импорт без # noqaPY-3.3удалить; ruff check --fix делает автоматически
# noqa: F401 без обоснованияPY-RUFF-3# noqa: F401 # justify: side-effect import
Ручная расстановка порядка импортовPY-3.3ruff check --fix (I001)
from logging import getLogger, info, debug, ...PY-3.4import logging + logging.getLogger(__name__)
Конфиг isort в .isort.cfg отдельно от ruffPY-RUFF-1[tool.ruff.lint.isort] в pyproject.toml
select = [] в ruff без "I" (isort off)PY-RUFF-X1добавить "I" в select

Куда дальше

  • python/naming.md — snake_case, PascalCase, константы UPPER_SNAKE_CASE, Pydantic id_ + alias.
  • python/type-hints.md — X | None вместо Optional[X], Protocol для портов, mypy --strict.
  • python/ruff-mypy.md — полная настройка pyproject.toml: select, line-length, mypy в CI.
  • Стандарты → Code Style — хаб языковых биндингов: Java, Node, Python, Go.