Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем нужен 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 нужен потому что:
- Находит ошибки до production — экономит деньги и время
- Улучшает качество кода — код становится понятнее
- Помогает IDE — автодополнение и подсказки работают лучше
- Документирует код — типы это живая документация
- Облегчает рефакторинг — все проверяется автоматически
- Предотвращает регрессии — пойманные ошибки не повторяются
В больших проектах mypy — это не опция, а необходимость. Современный Python разработчик должен уметь работать с типами и mypy.