Строгий режим в Python: от типов до валидации
Основное решение: строгая статическая типизация с mypy -strict
Как сделать код полностью типизированным и избежать ошибок типов?
Наиболее эффективный способ включить строгий режим в Python - использовать статический анализатор типов mypy с флагом --strict. Этот флаг включает все возможные проверки: обязательные аннотации для всех функций, запрет на использование Any, проверку совместимости типов и многое другое.
Пример файла example.py:
def greet(name) -> str:
return "Hello, " + name
age = 25
if age > 18:
result = greet(age) # Ошибка: ожидается str, передаётся int
Strict python (строгий режим (strict) в python)
Запуск mypy в строгом режиме:
mypy --strict example.py
example.py:5: error: Argument 1 to "greet" has incompatible type "int"; expected "str" Found 1 error in 1 file (checked 1 source file)
Для исправления необходимо явно аннотировать аргумент и переменную:
def greet(name: str) -> str:
return "Hello, " + name
age: int = 25
if age > 18:
result = greet(str(age)) # Теперь корректно
Проблема: Сторонние библиотеки могут не иметь аннотаций типов, что вызовет ошибки mypy.
Решение: Добавить файл pyproject.toml или mypy.ini для игнорирования конкретных модулей:
[mypy]
strict = True
ignore_missing_imports = True
[mypy-numpy.*]
ignore_missing_imports = True
Вариант 1: zip(strict=True) для контроля длины итераций
Как избежать незаметного обрезания списков при объединении?
Начиная с Python 3.10, функция zip() поддерживает аргумент strict=True, который вызывает ValueError, если итерируемые объекты разной длины.
names = ["Alice", "Bob", "Charlie"]
ages = [30, 25]
for name, age in zip(names, ages, strict=True):
print(f"{name}: {age}")
ValueError: zip() argument 2 is shorter than argument 1
Без strict ошибка прошла бы незаметно:
for name, age in zip(names, ages):
print(f"{name}: {age}")
Alice: 30 Bob: 25 # Charlie пропущен без уведомления
Проблема: strict=True доступен только в Python 3.10 и новее.
Решение: Использовать условную проверку или обёртку для старых версий.
Вариант 2: Замороженные датаклассы и __slots__
Как предотвратить случайное изменение атрибутов и экономить память?
Декоратор @dataclass(frozen=True) создаёт неизменяемые объекты. Добавление __slots__ = True запрещает создание новых атрибутов и уменьшает потребление памяти.
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Point:
x: float
y: float
p = Point(1.0, 2.0)
p.x = 3.0 # Ошибка: cannot assign to field 'x'
p.z = 0.0 # Ошибка: 'Point' object has no attribute 'z'
Проблема: Полная неизменяемость может быть неудобна для объектов, которые должны обновляться.
Решение: Использовать __slots__ без frozen, чтобы предотвратить динамические атрибуты, но разрешить изменения.
Вариант 3: Преобразование предупреждений в ошибки
Как заставить интерпретатор прекращать выполнение при наличии предупреждений?
Модуль warnings позволяет превратить все предупреждения в исключения с помощью filterwarnings('error'). Это усиливает контроль над кодом.
import warnings
warnings.filterwarnings('error')
import numpy as np
arr = np.arange(10)
arr[-5:] # Предупреждение о срезе с отрицательным шагом? (не критично)
# Но, например:
warnings.warn("Test warning")
UserWarning: Test warning Traceback (most recent call last): ... UserWarning: Test warning
Можно также использовать флаг интерпретатора:
python -W error script.py
Проблема: Многие библиотеки генерируют безвредные предупреждения (например, DeprecationWarning), которые могут прервать выполнение.
Решение: Применять выборочные фильтры:
warnings.filterwarnings('error', category=DeprecationWarning)
warnings.filterwarnings('ignore', category=UserWarning, module='numpy')
Вариант 4: Строгая валидация с Pydantic
Как гарантировать корректность данных при внешнем вводе?
Библиотека pydantic (начиная с v2) поддерживает параметр strict=True в моделях, что запрещает неявное преобразование типов. Например, строка "123" не будет автоматически превращена в число.
from pydantic import BaseModel
class User(BaseModel, strict=True):
name: str
age: int
user = User(name="Alice", age="25") # Ошибка: age должен быть int, а не str
pydantic_core.ValidationError: 1 validation error for User age Input should be a valid integer [type=int_type, input_value='25', input_type=str]
Проблема: Строгий режим pydantic может быть излишним для внутренних данных, где преобразование допустимо.
Решение: Использовать strict только для точек входа (API, файлы конфигурации).
Расширенные примеры строгого режима
1. Настройка mypy --strict через pyproject.toml с игнорированием неаннотированных библиотек
# pyproject.toml
[tool.mypy]
strict = true
ignore_missing_imports = true
# Игнорировать только определённые модули
[[tool.mypy.overrides]]
module = ["numpy.*", "pandas.*"]
ignore_missing_imports = true
Результат: mypy не ругается на импорты numpy, но проверяет весь остальной код.
2. zip(strict=True) с генераторами и разными типами последовательностей
# Генератор и список разной длины
gen = (x**2 for x in range(5))
lists = [10, 20, 30]
try:
for a, b in zip(gen, lists, strict=True):
print(a, b)
except ValueError as e:
print(f"Ошибка: {e}")
0 10 1 20 2 30 Ошибка: zip() argument 2 is shorter than argument 1
Генератор выдал 3 элемента, список содержит 3 элемента, но после их исчерпания генератор продолжает? На самом деле после третьего элемента генератор выдаёт ещё один элемент (3**2=9), но strict=True обнаруживает, что список закончился раньше. Важно: строгость проверяется после каждого шага.
3. Замороженный датакласс с наследованием и пользовательскими методами
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class Vehicle:
wheels: int
@dataclass(frozen=True, slots=True)
class Car(Vehicle):
model: str
car = Car(wheels=4, model="Tesla")
# car.wheels = 3 # Ошибка: frozen
# car.color = "red" # Ошибка: slots
Результат: объект полностью неизменяем, дополнительные атрибуты запрещены.
4. Превращение предупреждений в ошибки с фильтрацией по модулю
import warnings
warnings.filterwarnings('error', category=DeprecationWarning)
warnings.filterwarnings('ignore', category=DeprecationWarning, module='.*\.legacy_module')
# Пример вызова устаревшей функции
import numpy as np
np.matrix([[1,2],[3,4]]) # В NumPy 1.25+ это вызывает DeprecationWarning, но он перехватывается?
# Если модуль numpy не входит в игнорируемый, будет ошибка
Результат: DeprecationWarning из numpy превращается в исключение, если не указан фильтр для модуля.
5. Pydantic strict с кастомными валидаторами и вложенными моделями
from pydantic import BaseModel, field_validator
class Address(BaseModel, strict=True):
city: str
zip_code: str
class Person(BaseModel, strict=True):
name: str
age: int
address: Address
@field_validator('age')
@classmethod
def check_adult(cls, v):
if v < 18:
raise ValueError('Must be adult')
return v
# Проверка
try:
person = Person(name="Alice", age=17, address=Address(city="NY", zip_code="10001"))
except Exception as e:
print(e)
1 validation error for Person age Value error, Must be adult [type=value_error, input_value=17, input_type=int]
Возраст 17 не проходит валидацию, хотя тип int корректен.