← Назад к вопросам

Зачем нужен mypy?

1.8 Middle🔥 231 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен mypy?

mypy — это инструмент статического анализа типов для Python. Это один из самых важных инструментов в современной разработке, который помогает найти ошибки ДО того как код попадет в production.

Проблема: Динамическая типизация в Python

Python — язык с динамической типизацией. Это гибко, но опасно:

# Этот код работает до момента вызова
def add_numbers(a, b):
    return a + b

# Работает с числами
result = add_numbers(5, 3)  # Результат: 8

# Но может сломаться с неожиданными типами
result = add_numbers("Hello", 5)  # TypeError: can only concatenate str (not "int") to str

# Даже хуже - в большом приложении это обнаружится только в production!
data = get_user_data()  # Что это вернет?
user_id = data['id']   # А это существует?
print(user_id.upper()) # А это метод?

Решение: Type Hints и mypy

Type Hints — аннотации типов

Python 3.5+ позволяет указывать типы:

def add_numbers(a: int, b: int) -> int:
    return a + b

# Сразу видно какие типы ожидаются и что вернется

mypy проверяет эти аннотации

mypy myproject/

Результат:

error: Argument 1 to "add_numbers" has incompatible type "str"; expected "int"
error: Incompatible return value type (got "float", expected "int")

mypy найдет ошибки ДО запуска кода!

Зачем это нужно

1. Ловля ошибок на этапе разработки

# ❌ Без mypy - ошибка обнаруживается в production
def process_user(user_id):
    user = get_user(user_id)  # Может вернуть None
    email = user.email  # AttributeError если None
    send_email(email)

# ✅ С mypy - ошибка обнаруживается при разработке
def process_user(user_id: int) -> str:
    user: Optional[User] = get_user(user_id)  # Явно указано что может быть None
    if user is None:
        raise ValueError("User not found")
    email: str = user.email
    return email

# mypy скажет:
# error: Item "None" has no attribute "email"

2. Самодокументирующийся код

# Без типов - требуется читать документацию
def calculate_total(items, tax_rate, discount):
    pass

# С типами - всё ясно
def calculate_total(items: list[float], tax_rate: float, discount: float) -> float:
    return sum(items) * (1 + tax_rate) - discount

3. Лучшая поддержка в IDE

# IDE знает типы и может помочь:
user: User = get_user(1)
user.  # IDE покажет все доступные методы и атрибуты

4. Предотвращение дорогих ошибок

# Типичная ошибка в production
def process_payment(amount):
    # Получаем сумму из API
    total = get_amount()  # Вернула строку вместо int!
    result = amount + total  # Конкатенация вместо сложения!
    return result

# process_payment(100)  # Вернет "100500" вместо 600

# С mypy все проверяется заранее
def process_payment(amount: int) -> int:
    total: int = get_amount()  # mypy требует int
    result: int = amount + total
    return result

Примеры использования mypy

Базовые типы

# Примитивные типы
name: str = "John"
age: int = 25
salary: float = 5000.50
is_active: bool = True

# Коллекции
names: list[str] = ["John", "Jane"]
scores: tuple[int, int, int] = (10, 20, 30)
mapping: dict[str, int] = {"a": 1, "b": 2}

# Опциональные значения (может быть None)
user: Optional[User] = None

# Union типы (один из нескольких)
status: int | str = 200  # или "OK"

Функции

from typing import Callable, Optional, Union

# Функция которая принимает функцию
def apply_operation(a: int, b: int, op: Callable[[int, int], int]) -> int:
    return op(a, b)

apply_operation(5, 3, lambda x, y: x + y)  # ✓ OK
apply_operation(5, 3, lambda x: x + 1)     # ✗ mypy ошибка

# Переменное количество аргументов
def print_items(*args: str) -> None:
    for item in args:
        print(item)

print_items("a", "b", "c")  # ✓ OK
print_items("a", 1, "c")     # ✗ mypy ошибка

Классы

from dataclasses import dataclass

@dataclass
class User:
    id: int
    name: str
    email: str
    age: Optional[int] = None

user = User(id=1, name="John", email="john@example.com")
user.name = 123  # ✗ mypy ошибка: str expected

class Repository:
    def get_user(self, user_id: int) -> Optional[User]:
        pass
    
    def save_user(self, user: User) -> None:
        pass

Generics (универсальные типы)

from typing import Generic, TypeVar

T = TypeVar('T')

class Cache(Generic[T]):
    def __init__(self) -> None:
        self._cache: dict[str, T] = {}
    
    def get(self, key: str) -> Optional[T]:
        return self._cache.get(key)
    
    def set(self, key: str, value: T) -> None:
        self._cache[key] = value

# Использование
int_cache: Cache[int] = Cache()
int_cache.set("count", 42)  # ✓ OK
int_cache.set("count", "42")  # ✗ mypy ошибка

Конфигурация mypy

Файл mypy.ini или pyproject.toml

[mypy]
python_version = 3.11
warn_return_any = True
warn_unused_configs = True
disallow_untyped_defs = True  # Требовать типы для всех функций
disallow_incomplete_defs = True

[mypy-third_party_lib.*]
ignore_missing_imports = True  # Пропускать библиотеки без типов

Или в pyproject.toml

[tool.mypy]
python_version = "3.11"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true

Интеграция в CI/CD

# В .github/workflows/ci.yml
- name: Run mypy
  run: mypy myproject/

# Или в pre-commit hook
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.5.0
    hooks:
      - id: mypy
        args: [--strict]

Реальный пример: Django проект

# myapp/models.py
from django.db import models
from typing import Optional

class User(models.Model):
    email: str = models.EmailField()
    name: str = models.CharField(max_length=100)
    age: Optional[int] = models.IntegerField(null=True, blank=True)

# myapp/views.py
from django.http import JsonResponse
from django.views import View

class UserView(View):
    def get(self, request, user_id: int) -> JsonResponse:
        user: Optional[User] = User.objects.filter(id=user_id).first()
        if user is None:
            return JsonResponse({"error": "Not found"}, status=404)
        
        return JsonResponse({
            "id": user.id,
            "name": user.name,
            "email": user.email
        })

# myapp/services.py
def send_email_to_user(user_id: int) -> bool:
    user: Optional[User] = User.objects.filter(id=user_id).first()
    if not user:
        return False
    
    # mypy знает что user точно User, не None
    send_mail("Subject", "Body", "from@example.com", [user.email])
    return True

Проблемы и решения

Проблема: Библиотеки без типов

# Какой-то package не имеет типов
import old_library  # mypy скажет: "No typeshed stub"

# Решение: Пропустить проверку
# mypy: ignore
from old_library import some_func

Проблема: Слишком строгие проверки

# Если mypy слишком придирчив
# Можно постепенно вводить типы
# Начать с strict mode только для новых файлов

Заключение

mypy нужен потому что:

  1. Находит ошибки до production — экономит деньги и время
  2. Улучшает качество кода — код становится понятнее
  3. Помогает IDE — автодополнение и подсказки работают лучше
  4. Документирует код — типы это живая документация
  5. Облегчает рефакторинг — все проверяется автоматически
  6. Предотвращает регрессии — пойманные ошибки не повторяются

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