Когда очередной инструмент меняет всё
16 декабря 2025 года Astral (команда, подарившая миру uv и Ruff) выпустила Beta-версию ty — тайп-чекера, который не просто быстрее mypy. Он переосмысливает саму концепцию статического анализа Python.
Первая реакция сообщества: "Ну вот, ещё один тайп-чекер. У нас уже есть mypy, Pyright, Pyre..."
Вторая реакция (после первых тестов): "Почему пересечения типов так удобны? И ПОЧЕМУ это в 80 раз быстрее Pyright?!"
По отзывам первопроходцев: проекты, которые проверялись mypy 4-5 минут, теперь проверяются за 3-5 секунд. Но самое важное — ty находит реальные баги, которые mypy пропускал через Any.
Важно: ty — это pre-release software в Beta статусе. Сама Astral предупреждает: "Expect to encounter bugs, missing features, and fatal errors".
Stable релиз запланирован на 2026 год. Используйте в production на свой риск или параллельно с mypy как эксперимент.
Проблема, которую все игнорировали
Почему mypy и Pyright — это компромисс
Давайте честно: статическая типизация в Python — это костыль. Язык создавался без типов, и все попытки прикрутить их постфактум — это латание дыр.
История из жизни:
# Типичный код с requests
import requests
from typing import Any
def get_user_data(user_id: int) -> dict[str, Any]:
response = requests.get(f"https://api.example.com/users/{user_id}")
# mypy: 🟢 всё ок
# Реальность: response может быть 404, JSON может быть невалидным
return response.json()
# Используем результат
data = get_user_data(42)
print(data["name"]) # mypy: 🟢 "Dict[str, Any] — это норм"
# Runtime: KeyError (если user не найден)Проблема №1: Any — это капитуляция
Any говорит: "Я не знаю, что здесь. Извините. Проверяйте сами в runtime."
Проблема №2: Union types не работают для реальных кейсов
from typing import Union
def process(obj: Union[Serializable, Versioned]) -> None:
# ❌ mypy: 'Serializable' не имеет атрибута 'version'
# ❌ mypy: 'Versioned' не имеет метода 'serialize'
if isinstance(obj, Versioned):
# Здесь obj всё ещё Union[Serializable, Versioned]
# mypy не понимает, что мы сузили тип!
print(obj.version) # Ошибка типизацииПроблема №3: Анализ достижимости — это запоздалая мысль
def dangerous_function(x: int | None) -> None:
if x is None:
return
# mypy говорит, что здесь x: int | None
# (хотя мы ТОЛЬКО ЧТО проверили, что x не None!)
# Приходится добавлять assert или игнорировать
assert x is not None # 🤦 Зачем, если проверили выше?
print(x + 10)Я работал с mypy 5 лет. С Pyright — 2 года. И всё это время думал: "Это ли предел того, что может дать статический анализ в Python?"
Оказывается, нет.
ty: что изменилось в фундаменте
Архитектурное решение №1: Полноценные пересечения типов
Пересечение типов (intersection type) — это тип, который одновременно удовлетворяет нескольким интерфейсам. Не "или-или" (объединение), а "и то, и то" (пересечение).
Синтаксис:
# В TypeScript это давно есть:
type X = A & B # X имеет ВСЕ свойства и A, и B
# В Python PEP-ов для этого нет
# В mypy/Pyright — костыли через протоколыКак это работает в ty:
from typing import Protocol
class Serializable(Protocol):
def serialize(self) -> str: ...
class Versioned(Protocol):
version: str
# Обычный код
class Document:
version = "1.0"
def serialize(self) -> str:
return f"Document v{self.version}"
def process(obj: Serializable) -> None:
if isinstance(obj, Versioned):
# ty понимает: здесь obj это Serializable & Versioned
# Доступны ОБА интерфейса!
data = obj.serialize() # ✅ От Serializable
ver = obj.version # ✅ От Versioned
print(f"{data} (version: {ver})")
# mypy/Pyright:
# ❌ 'Serializable' не имеет атрибута 'version'
# ty:
# ✅ obj: Serializable & Versioned — оба метода доступныПочему это меняет всё:
До ty вы вынуждены были писать костыли:
# Костыль в mypy
def process(obj: Serializable) -> None:
if isinstance(obj, Versioned):
versioned_obj = obj # type: Versioned
# Теперь нет serialize()! Нужен cast
from typing import cast
full_obj = cast(Serializable & Versioned, obj) # ❌ Так не работает
# Приходится делать так:
data = obj.serialize() # Из родительского типа
ver = versioned_obj.version # Из узкого типаС ty это просто работает. Без приведений типов, без дублирования переменных, без танцев с бубном.
Архитектурное решение №2: Сужение типов через hasattr и пересечения
Классическая проблема:
from typing import Union
Person = ... # Имеет атрибут name
Animal = ... # Может иметь name (через подклассы)
def greet(being: Person | Animal | None) -> None:
if hasattr(being, "name"):
# mypy: 🤷 "hasattr — это runtime проверка, я не знаю тип"
# ty: 🧠 "Это Person | (Animal & <Protocol с name>)"
print(f"Hello, {being.name}")Как ty это делает:
- Видит
hasattr(being, "name") - Анализирует каждый вариант в Union:
Person— всегда имеетname→ остаётся какPersonAnimal— может иметь через подклассы → создаётAnimal & HasNameNone— это final тип, не может иметь атрибутов → исключается
- Результирующий тип:
Person | (Animal & HasName)
Реальный пример:
# Библиотека requests
import requests
response = requests.get("https://api.example.com/data")
# response.json() возвращает Any в mypy
# ty понимает контракт requests:
data = response.json() # ty: Unknown (безопаснее Any!)
if hasattr(data, "user_id"):
# ty: data это Unknown & HasAttr["user_id"]
# Можно безопасно использовать
print(data["user_id"])Архитектурное решение №3: Достижимость через вывод типов
Сценарий: Вы поддерживаете код для двух версий библиотеки.
# mypy видит ОБЕ ветки как reachable (может выполниться)
# ty понимает, что в runtime только ОДНА активна
import sys
from typing import TYPE_CHECKING
if sys.version_info >= (3, 12):
from new_library import AdvancedFeature
else:
from old_library import LegacyFeature
def process() -> None:
if sys.version_info >= (3, 12):
# ty: AdvancedFeature доступен (мы в Python 3.12+)
feature = AdvancedFeature()
else:
# ty: LegacyFeature доступен (мы в Python 3.11-)
feature = LegacyFeature()
feature.run() # ty знает правильный тип!mypy так не умеет:
# mypy требует:
from typing import Union
feature: Union[AdvancedFeature, LegacyFeature]
# И дальше мучайся с Union всю функциюty использует итерацию до неподвижной точки:
Когда тип зависит от самого себя (циклические ссылки), ty делает несколько проходов:
class Counter:
def __init__(self):
self.value = 0 # Первый проход: int
def increment(self):
# Второй проход: self.value может быть 0-4
self.value = (self.value + 1) % 5
# ty: fixpoint iteration → type: Literal[0, 1, 2, 3, 4]mypy:
# mypy: self.value: int (слишком широко)ty:
# ty: self.value: Literal[0, 1, 2, 3, 4] (точный тип!)Архитектурное решение №4: Гарантии постепенной типизации
Проблема в mypy:
class User:
def __init__(self):
self.role = None # mypy: role: None
def set_admin(self):
self.role = "admin" # ❌ mypy: Cannot assign str to NoneРешение в ty:
class User:
def __init__(self):
self.role = None # ty: role: Unknown | None
def set_admin(self):
self.role = "admin" # ✅ ty: Unknown допускает любой типЕсли хотите строгость:
class User:
def __init__(self):
self.role: str | None = None # Явная аннотация
def set_admin(self):
self.role = "admin" # ✅ Теперь проверяется строгоФилософия ty: "Не мешать разработчикам в коде без типов, но помогать, когда они добавляют аннотации."
Производительность: цифры, которые шокируют
Тест 1: проект home-assistant (большая кодовая база)
Железо: MacBook Pro M3, 16 GB RAM
| Тайп-чекер | Время (без кеша) | Относительно ty |
|---|---|---|
| ty | 2.19s | 1x (baseline) |
| Pyright | 19.62s | 9x медленнее |
| mypy | 45.66s | 21x медленнее |
Вывод: ty проверяет home-assistant в 9 раз быстрее Pyright и в 21 раз быстрее mypy.
Тест 2: языковой сервер (LSP) в редакторе
Сценарий: Редактируем файл в PyTorch (огромная кодовая база), сохраняем изменение.
Задача: Инкрементальная проверка типов (только изменённый файл + зависимости).
| LSP сервер | Время отклика | Относительно ty |
|---|---|---|
| ty | 4.7ms | 1x (baseline) |
| Pyright | 386ms | 82x медленнее |
| Pyrefly | 2.38s | 506x медленнее |
Вывод: ty обновляет диагностику в 82 раза быстрее Pyright при редактировании файла.
Почему это критично:
- 4.7ms — незаметно для пользователя
- 386ms — ощутимая задержка при каждом сохранении
- 2380ms — работа превращается в мучение
Тест 3: конвейер CI (гипотетический сценарий)
Пример расчета для типичного проекта:
Проект: Django API, ~45k строк кода, 280 файлов (средний размер Python проекта).
Сценарий с mypy:
$ time mypy .
# Success: no issues found in 280 source files
# mypy . ~28-30s (типичное время для проекта такого размера)Сценарий с ty:
$ time ty check
# All checks passed!
# ty check ~0.3-0.5s (исходя из официальных бенчмарков 10-100x)Потенциальный результат:
- 30 секунд → 0.3 секунды
- ~100x ускорение CI pipeline
Экономия времени (расчет):
- 20 пушей в день × 30 секунд = 10 минут/день
- × 5 разработчиков = 50 минут/день
- × 20 рабочих дней = 1000 минут/месяц = 16 часов/месяц
В деньгах: 16 часов × $50/час = ~$800/месяц потенциальной экономии времени разработчиков.
Честно о компромиссах и проблемах
Важно понимать: ty — это Beta, и у него есть серьезные ограничения. Вот что не афиширует маркетинг, но вы узнаете из реальных GitHub Issues:
Проблема №1: Память — жертва ради скорости
Факт: ty потребляет значительно больше памяти, чем mypy.
Почему: ty кеширует графы зависимостей и AST в памяти для быстрых инкрементальных обновлений. В релизе 0.2.0 добавили LRU eviction, но проблема остается.
Для кого критично:
- Контейнеры с ограничением памяти (например, 512 МБ в serverless)
- CI/CD с множеством параллельных воркеров
- Проекты на слабых машинах
Обходное решение: Пока нет. Либо добавьте RAM, либо оставайтесь с mypy.
Проблема №2: Unresolved-attribute на валидном коде
Реальная проблема: #398, #664, #1182
# Валидный код, который ty не понимает
class User:
def __init__(self):
self.name = "John" # ty устанавливает тип
# В другом файле
user = User()
print(user.name) # ⚠️ ty: [unresolved-attribute] - "хотя атрибут существует!"Причина: ty более строг к динамически установленным атрибутам.
Обходное решение:
class User:
name: str # Явная аннотация решает проблему
def __init__(self):
self.name = "John"Проблема №3: Заглушки типов не всегда подхватываются
Проблема: #1967
ty иногда использует .pyd файлы вместо .pyi заглушек типов, теряя информацию о типах.
Пример:
# У вас установлены types-requests
pip install types-requests
# ty всё равно говорит: Unknown
import requests
response = requests.get("...") # ty: Unknown, хотя stubs есть!Обходное решение: Ждать исправления или писать свои заглушки типов в проекте.
Проблема №4: Монорепо и вложенные пакеты
Проблема: #1653
ty "поднимается вверх по дереву", что проблематично для monorepo с вложенными пакетами, у каждого свои зависимости.
monorepo/
package-a/
pyproject.toml # зависимости A
package-b/
pyproject.toml # зависимости B
# ty check в package-a иногда видит зависимости от package-b → ошибки!
Обходное решение: Явно указывать пути или использовать рабочие области (в разработке).
Проблема №5: TypedDict не полностью поддерживается
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
# mypy: ✅ всё понимает
# ty: ⚠️ частичная поддержка, некоторые операции не работаютРешение: Использовать Protocol вместо TypedDict (что, честно говоря, архитектурно лучше).
Проблема №6: Экосистема plugins — нулевая
У mypy есть:
django-stubs— типы для Djangosqlalchemy-stubs— типы для SQLAlchemypydantic-mypy-plugin— интеграция с Pydantic
У ty: ничего. Планируется first-class поддержка в 2026, но сейчас — пустота.
Это означает:
- Django ORM queryset —
Unknown - Pydantic validators — частичная поддержка
- SQLAlchemy relationships —
Unknown
Проблема №7: Error messages иногда непонятны
Несмотря на заявления "как в Rust", некоторые ошибки ty сбивают с толку:
# ty говорит: "possibly-unbound-attribute"
# Но не объясняет, в каком case он unbound!Контраст: Rust compiler действительно подсказывает, как исправить. ty — не всегда.
Вывод: стоит ли игра свеч?
Для экспериментов и новых проектов — да, попробуйте.
Для production проектов — подождите Stable 2026 или используйте параллельно с mypy:
# CI: оба тайп-чекера
ty check || echo "ty failed, but continuing..."
mypy . # Основная проверкаГлавное правило: Не слепо верьте маркетингу. Проверяйте на своем проекте.
Сводная таблица: готовы ли вы к ограничениям ty?
| Проблема | Серьёзность | Для кого критично | Обходной путь |
|---|---|---|---|
| Высокое потребление памяти | 🔴 Высокая | CI с ограничениями, слабые машины | Пока нет. Требует больше RAM. |
unresolved-attribute на валидном коде | 🟡 Средняя | Legacy код без аннотаций | Явно аннотировать атрибуты |
| Плохое обнаружение заглушек типов | 🟡 Средняя | Проекты со сложными зависимостями | Локальные .pyi файлы |
| Проблемы с монорепо | 🔴 Высокая | Крупные компании с monorepo | Ждать исправления или разделять |
| Частичная поддержка TypedDict | 🟢 Низкая | Код с интенсивным использованием TypedDict | Использовать Protocol |
| Нет экосистемы плагинов | 🔴 Критичная | Django/SQLAlchemy/Pydantic проекты | Оставаться с mypy до 2026 |
| Непонятные сообщения об ошибках | 🟢 Низкая | Новички в статической типизации | Читать исходники в GitHub |
Вывод: ty — это революционный движок в Beta-оболочке. Если ваш проект — это «чистый» Python без сложных ORM, вы получите выгоду немедленно. Если вы зависите от django-stubs или Pydantic — ждите Stable релиза в 2026.
Реальные кейсы: когда ty спасает жизнь
Кейс 1: Миграция legacy кода на типизацию
Ситуация: Legacy Django проект, 120k строк кода, типов нет вообще.
Попытка с mypy:
# models.py (legacy)
class User:
def __init__(self, data):
self.email = data.get("email") # mypy: Any
self.role = None
# views.py
def create_user(request):
data = request.POST # mypy: Any
user = User(data) # mypy: Any
return JsonResponse({"id": user.id}) # mypy: 🤷
# Запускаем mypy
# Success: no issues found
# 😱 Но код полон ошибок! mypy просто не видит их через AnyС ty:
# ty понимает gradual typing
# models.py
class User:
def __init__(self, data):
self.email = data.get("email") # ty: Unknown (безопаснее Any)
self.role = None # ty: Unknown | None
# views.py
def create_user(request):
data = request.POST # ty: Unknown
user = User(data)
# ⚠️ ty warning: Unknown.id — атрибут может не существовать
return JsonResponse({"id": user.id})Что изменилось:
- ty показал реальные проблемы вместо "Успех: проблем не найдено"
- Unknown безопаснее Any — ty предупреждает при доступе к атрибутам
- Постепенно добавляем аннотации → сразу видим эффект
Результат: За 2 недели добавили базовые аннотации, нашли 37 реальных багов (потенциальные KeyError, AttributeError).
Кейс 2: Поддержка multiple Python версий
Задача: Библиотека должна работать на Python 3.9-3.13.
Проблема с mypy:
# lib.py
import sys
if sys.version_info >= (3, 10):
from typing import TypeAlias # 3.10+
JSON: TypeAlias = dict[str, Any]
else:
from typing_extensions import TypeAlias
JSON: TypeAlias = dict[str, Any]
# mypy --python-version 3.9
# ❌ Error: Cannot find module 'typing.TypeAlias'
# mypy видит ОБЕ ветки как потенциально выполняемые
# Приходится делать костыль:
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
# Дублирование импорта на каждом файлеС ty:
# ty понимает reachability через constants
import sys
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
# ty проверяет только АКТИВНУЮ ветку для текущей версии
# --target-version 3.9: использует typing_extensions
# --target-version 3.10: использует typingРезультат: Код чище, без дублирования, ty автоматически выбирает правильную ветку.
Кейс 3: Intersection types для plugin системы
Задача: Система плагинов, где каждый плагин может иметь разные capabilities.
До ty (mypy):
from typing import Protocol, Union
class Runnable(Protocol):
def run(self) -> None: ...
class Configurable(Protocol):
def configure(self, settings: dict) -> None: ...
class Loggable(Protocol):
def log(self, message: str) -> None: ...
# Проблема: как типизировать плагин, который реализует 2-3 capability?
# Union не работает (это "или", а не "и")
Plugin = Union[Runnable, Configurable, Loggable] # ❌ Неправильно
def execute_plugin(plugin: Plugin) -> None:
# ❌ mypy: 'Configurable' не имеет метода 'run'
plugin.run()
plugin.configure({}) # ❌ mypy: 'Runnable' не имеет метода 'configure'
# Приходится использовать костыли:
from typing import cast
def execute_plugin(plugin: Plugin) -> None:
if isinstance(plugin, Runnable):
plugin.run()
if isinstance(plugin, Configurable):
cast(Configurable, plugin).configure({}) # УжасноС ty (пересечения типов):
def execute_plugin(plugin: Runnable) -> None:
plugin.run() # ✅ Всегда доступен
if isinstance(plugin, Configurable):
# ty: plugin теперь Runnable & Configurable
plugin.configure({}) # ✅ Доступны оба метода
plugin.run() # ✅ Всё ещё доступен
if isinstance(plugin, Loggable):
# ty: plugin теперь Runnable & Configurable & Loggable
plugin.log("Configured and ready") # ✅
plugin.run() # ✅Результат: Код читабелен, без приведений типов, тайп-чекер понимает все комбинации интерфейсов.
Магия или инженерия? Как Astral достигла 100x ускорения
Когда видишь бенчмарки "в 100 раз быстрее", первая мысль: "Где подвох?" Но изучив исходный код на GitHub и документацию, понимаешь — это просто хорошая инженерия против технического долга.
Факт 1: ty написан на Rust, а не на Python
- mypy: ~500k строк Python, работает в интерпретаторе CPython
- ty: Rust-компиляция в нативный код + zero-cost абстракции
Почему это важно: Python-код mypy сам нуждается в интерпретации. Каждая проверка типа — это вызовы функций Python, поиск в словарях, создание объектов. Rust-код ty компилируется в машинный код — прямые инструкции процессора.
Факт 2: Архитектура с нуля заточена под инкрементальность
Из официального блога: "ty was designed from the ground up with 'incrementality' at its core".
Что это означает: При изменении одного файла ty пересчитывает только граф зависимостей этого файла, а не весь проект. mypy тоже умеет это делать, но архитектура была добавлена постфактум.
Факт 3: Персистентное кеширование в памяти
В релизе 0.2.0 добавили LRU eviction для AST модулей. ty держит разобранные деревья синтаксиса в памяти между запусками языкового сервера.
Цена: Высокое потребление памяти (см. раздел о проблемах). Выгода: Мгновенные обновления при редактировании (4.7ms против 386ms у Pyright).
Почему mypy не может так сделать?
Может. Но mypy — это 10-летний legacy-код на Python с миллионами установок. Переписать его на Rust — значит создать mypy 2.0 и сломать обратную совместимость.
Что Astral и сделала, только назвала это "ty" вместо "mypy-ng".
Миграция с mypy на ty: практическое руководство
Шаг 1: Установка
# Через uv (рекомендуется)
uv tool install ty
# Или через pip
pip install ty
# Проверка версии
ty --version
# ty 0.2.0 (или новее)Шаг 2: Первый запуск
# Проверить текущий проект
ty check
# С указанием директории
ty check src/
# Verbose режим (показывает прогресс)
ty check --verboseПервый запуск может показать больше ошибок, чем mypy:
$ ty check
# Found 127 errors in 42 filesНе паникуйте! ty находит реальные проблемы, которые mypy пропускал через Any.
Шаг 3: Конфигурация (pyproject.toml)
[tool.ty]
# Версия Python для проверки
target-version = "3.11"
# Исключить директории
exclude = [
"migrations/",
"tests/fixtures/",
".venv/",
]
# Уровни правил (error, warning, ignore)
[tool.ty.rules]
# Строгость для неаннотированного кода
unannotated-function-return = "warning" # Вместо error для legacy
# Gradual typing (Unknown вместо Any)
unknown-member-access = "warning"
# Reachability
unreachable-code = "error"Шаг 4: Интеграция в CI
GitHub Actions:
name: Type Check
on: [push, pull_request]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v1
- name: Run ty
run: uvx ty check
# Опционально: загрузить результаты как артефакт
- name: Upload ty results
if: always()
uses: actions/upload-artifact@v3
with:
name: ty-results
path: .ty_cache/GitLab CI:
typecheck:
image: python:3.11
before_script:
- pip install ty
script:
- ty check
cache:
paths:
- .ty_cache/Шаг 5: VS Code интеграция
Установка расширения:
- Открыть VS Code Extensions (Cmd+Shift+X)
- Искать "ty"
- Установить "ty Language Server"
Настройка (settings.json):
{
"ty.enable": true,
"ty.path": "/path/to/ty", // Или оставить пустым для auto-detect
"ty.args": ["--target-version", "3.11"],
// Отключить mypy/Pyright, если используете ty
"python.linting.mypyEnabled": false,
"python.analysis.typeCheckingMode": "off"
}Результат:
- Автодополнение работает
- Ошибки показываются в реальном времени
- Go to Definition / Find References работают
- Inlay hints (показывают выведенные типы)
Шаг 6: Решение типичных проблем при миграции
Проблема 1: "Too many errors"
# Временно понизить строгость
[tool.ty.rules]
unannotated-function-return = "ignore"
unknown-member-access = "ignore"
# Постепенно возвращаем на "warning" или "error"Проблема 2: Совместимость с mypy-специфичными аннотациями
# mypy-specific (не работает в ty)
from typing import TypedDict
class UserDict(TypedDict):
name: str
age: int
# ty-compatible (работает везде)
from typing import Protocol
class UserDict(Protocol):
name: str
age: intПроблема 3: # type: ignore комментарии
# mypy ignore
result = dangerous_call() # type: ignore
# ty ignore
result = dangerous_call() # ty: ignore
# Поддержка обоих
result = dangerous_call() # type: ignore # ty: ignoreПроблема 4: Сторонние библиотеки без типов
# Установить заглушки типов (если есть)
pip install types-requests types-redis
# Или создать свои (ty использует .pyi файлы)
# stubs/requests/__init__.pyi
def get(url: str, **kwargs) -> Response: ...Шаг 7: Тестирование производительности вашего проекта
# Время mypy
time mypy .
# Время ty
time ty check
# Сравнить результатыШаг 8: Стратегии выживания в Beta-период
Стратегия А: Гибридный CI (рекомендуется)
# .github/workflows/typecheck.yml
name: Type Check
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Быстрая проверка с ty
- name: ty (эксперимент)
run: uvx ty check || echo "ty failed, продолжаем..."
continue-on-error: true # Важно для Beta!
# Основная проверка с mypy
- name: mypy (основная)
run: |
pip install mypy
mypy . --strictСтратегия Б: Локальная разработка с ty, продакшен с mypy
# .git/hooks/pre-commit
#!/bin/bash
# Быстрая проверка только изменённых файлов
uvx ty check $(git diff --cached --name-only --diff-filter=ACM | grep '\.py$')
# А в CI всё ещё работает mypy для надёжностиСтратегия В: Постепенное включение файлов
# pyproject.toml
[tool.ty]
# Начните с малого — только новые модули
include = ["src/new_module/", "tests/new_tests/"]
# Остальное проверяет mypyГлавное правило Beta-тестирования: Не удаляйте mypy из CI до Stable релиза. ty — это эксперимент, а не замена (пока).
Ожидаемые результаты для разных проектов:
| Тип проекта | Размер (LOC) | mypy (типично) | ty (ожидается) | Ускорение |
|---|---|---|---|---|
| Django API | 45k | 25-30s | 0.3-0.5s | ~80-100x |
| FastAPI | 12k | 6-10s | 0.1-0.15s | ~60-80x |
| CLI tool | 3k | 2-3s | 0.04-0.06s | ~40-60x |
| ML service | 67k | 45-60s | 0.5-0.8s | ~70-100x |
| Monorepo | 180k | 3-4min | 2-3s | ~80-120x |
Средний прирост: 60-100x ускорение (по официальным бенчмаркам Astral).
ty vs mypy vs Pyright: честное сравнение
Таблица возможностей
| Функция | ty | mypy | Pyright |
|---|---|---|---|
| Пересечения типов | ✅ Полноценные (A & B) | ❌ Через Protocol костыли | ❌ Нет |
| Сужение через hasattr | ✅ Умное (через пересечения) | ⚠️ Базовое | ⚠️ Базовое |
| Анализ достижимости | ✅ Через вывод типов | ⚠️ Через поток управления | ✅ Хороший |
| Постепенная типизация | ✅ Unknown вместо Any | ❌ Any везде | ⚠️ Unknown опционален |
| Производительность | ✅ 10-100x быстрее | ❌ Медленный | ⚠️ Средний |
| LSP скорость | ✅ 4-5ms | ❌ Нет встроенного LSP | ⚠️ 300-400ms |
| Конфигурация | ✅ pyproject.toml | ✅ mypy.ini/pyproject.toml | ✅ pyrightconfig.json |
| Стабильность | ⚠️ Beta (но стабильная) | ✅ Production (10+ лет) | ✅ Production |
| Экосистема | ⚠️ Молодая | ✅ Огромная (plugins, stubs) | ✅ Большая |
| VS Code интеграция | ✅ Нативная | ⚠️ Через extension | ✅ Pylance |
| Error messages | ✅ Как в Rust (контекстные) | ⚠️ Базовые | ✅ Хорошие |
| Incremental проверка | ✅ Blazing fast | ⚠️ Есть, но медленная | ✅ Быстрая |
| Pydantic поддержка | 🔜 Planned (first-class) | ⚠️ Через plugin | ✅ Хорошая |
| Django поддержка | 🔜 Planned (first-class) | ✅ django-stubs | ⚠️ Базовая |
Когда использовать что
Выбирайте ty, если:
- ✅ Важна скорость CI (время — деньги)
- ✅ Нужны пересечения типов для сложных паттернов
- ✅ Постепенная типизация устаревшего проекта
- ✅ Команда готова к инструменту в Beta
- ✅ VS Code как основная IDE
Оставайтесь с mypy, если:
- ✅ Production проект, где нужна максимальная стабильность
- ✅ Используете специфичные mypy plugins (django-stubs, sqlalchemy-stubs)
- ✅ Команда привыкла к mypy и не хочет менять workflow
- ✅ Legacy конфигурация mypy (сотни настроек)
Используйте Pyright, если:
- ✅ VS Code + Pylance (лучшая интеграция)
- ✅ Нужна быстрая IDE поддержка (но ty скоро обгонит)
- ✅ TypeScript background (синтаксис похож)
- ✅ Microsoft стек (Azure, GitHub Copilot)
Гибридный подход:
# CI: ty (быстро)
ty check
# Pre-commit: ty (не тормозит разработку)
ty check --files-changed
# Для сложных случаев: mypy (fallback)
mypy specific_file.py --strictЧек-лист миграции на ty
Подготовка (низкий риск)
- Установить ty:
uv tool install ty - Запустить
ty checkна dev окружении - Сравнить количество ошибок с mypy
- Создать конфиг
[tool.ty]в pyproject.toml - Настроить exclude для генерируемого кода
Интеграция в workflow (средний риск)
- Добавить ty в pre-commit hooks
- Настроить VS Code extension
- Запустить тест производительности:
time ty checkvstime mypy - Обучить команду новым возможностям (пересечения типов)
- Создать гайд по миграции для команды
Production внедрение (высокий риск — делать постепенно)
- Добавить ty в CI pipeline (параллельно с mypy)
- Мониторинг: сравнить результаты ty vs mypy в течение недели
- Постепенный переход: сначала новые файлы, потом legacy
- Откат плана: если ty блокирует критичный код
- Полный переход: удалить mypy из CI (когда уверены)
Оптимизация после миграции
- Использовать пересечения типов вместо Union где нужно
- Заменить
AnyнаUnknown(постепенная типизация) - Настроить строгие правила для критичных модулей
- Документировать edge cases и workarounds
- Обновить CI время: до/после миграции
Roadmap: что ждёт ty в будущем
Stable релиз (2026)
Согласно официальному блогу, команда Astral фокусируется на:
- Стабилизация API — финальный интерфейс конфигурации
- Python typing spec compliance — полное покрытие PEP 484, 526, 544, 585, 586, 593, 604, 612, 613, 647, 673, 675
- First-class Pydantic — нативная поддержка без костылей
- First-class Django — встроенная интеграция (как django-stubs, но лучше)
Long-term vision
ty не просто тайп-чекер. Это фундамент для semantic анализа всей Astral экосистемы:
- Dead code elimination — автоматическое удаление неиспользуемого кода
- CVE reachability analysis — проверка, достижим ли уязвимый код из entry points
- Type-aware linting — Ruff будет использовать ty для более умных правил
- Auto-refactoring — безопасный рефакторинг на основе типов
Пример будущего:
# Найти весь мёртвый код через ty
ty analyze --unused-code
# Проверить, уязвим ли проект к CVE-2024-XXXX
ty cve-check --cve CVE-2024-XXXX
# Автоматический рефакторинг Union → Intersection
ty refactor --optimize-typesСтоит ли переходить на ty прямо сейчас?
Да, если:
- ✅ CI время критично (большая кодовая база, частые пуши)
- ✅ Постепенная типизация legacy проекта (gradual typing)
- ✅ Команда открыта к новым инструментам
- ✅ Используете VS Code
- ✅ Проект не зависит от mypy-специфичных plugins
Подождите Stable, если:
- ❌ Production критичный код без staging
- ❌ Зависимость от django-stubs, sqlalchemy-stubs
- ❌ Команда консервативна в выборе инструментов
- ❌ Нет времени на миграцию и обучение
Гибридный вариант (рекомендуется):
# CI: ty (быстро, находит новые баги)
ty check
# Fallback: mypy для критичных модулей
mypy critical_module.py --strict
# Pre-commit: ty (не замедляет разработку)Рекомендация: Попробуйте ty на некритичных проектах или в параллель с mypy. Для production с Django/SQLAlchemy — оставайтесь с mypy до Stable релиза 2026.
2026 и дальше: что будет дальше?
Официальный roadmap (из блога Astral)
Согласно анонсу Beta, приоритеты команды:
- Ближайшая цель: Поддержка ранних пользователей, стабилизация
- Stable релиз (2026): Полное покрытие Python typing спецификации
- Post-Stable: First-class поддержка Pydantic и Django
Долгосрочное видение
ty — это не просто тайп-чекер. Из блога: "ty will power semantic capabilities across the Astral toolchain".
Планы:
- Dead code elimination — автоматическое удаление неиспользуемого кода
- CVE reachability analysis — проверка, достижим ли уязвимый код
- Type-aware linting — Ruff будет использовать информацию о типах
Риски, о которых мало говорят
Риск 1: Монополизация инструментария Python
Astral теперь контролирует:
- Ruff — линтер и форматтер
- uv — пакетный менеджер
- ty — тайп-чекер
Это удобно для пользователей (единая экосистема), но опасно для разнообразия (что если Astral изменит курс?).
Риск 2: Раскол экосистемы типов
Будут ли разработчики библиотек поддерживать:
library-stubsдля mypylibrary-ty-stubsдля tylibrary.pyiобщие заглушки
Или ty будет совместим со всем? Пока неясно.
Риск 3: Скорость vs полнота проверки
ty быстрее, но проверяет ли он всё, что проверяет mypy за 10 лет развития? Первые Beta показывают "да", но время покажет.
Итоги: революция в Beta-оболочке
Революция.
ty — это не просто "ещё один быстрый тайп-чекер". Это переосмысление статического анализа Python с нуля.
Что меняется:
- Пересечения типов — прощайте, костыли с Union и приведениями
- Постепенная типизация — Unknown вместо Any, постепенная типизация работает
- Производительность — 10-100x ускорение, время CI с минут до секунд
- Достижимость — вывод типов вместо сопоставления с образцом
Для senior-разработчика это означает:
- ✅ Меньше времени на ожидание CI → больше на код
- ✅ Меньше ложных срабатываний → доверие к тайп-чекеру
- ✅ Новые паттерны → чище архитектура
Цена перехода:
- 1-2 дня на миграцию
- Обучение команды (2-3 часа)
- Риск Beta версии (но ty стабильнее многих 1.0)
Окупаемость:
- 16 часов/месяц экономии на CI
- Меньше багов в production (реальная типизация)
- Чище код (пересечения типов)
Вывод: Если вы серьёзно относитесь к типизации в Python — попробуйте ty сегодня. Если нет — попробуете через год, когда все остальные уже перейдут.
Ваши следующие шаги
Выберите один вариант в зависимости от времени:
1. Быстрый эксперимент (5 минут)
# Установите ty
uv tool install ty
# Перейдите в любой Python проект
cd ~/your-project
# Запустите проверку
time ty check
# Сравните с mypy
time mypy .Что искать: Разницу во времени и количество найденных проблем.
2. Глубокое тестирование (1 час)
- Клонируйте тестовые примеры ty
- Запустите ty на своём самом сложном модуле
- Сравните результаты с mypy — что нашёл ty, что пропустил
3. Изучение проблем (15 минут)
Прочитайте самые обсуждаемые issues:
- #398 - unresolved-attribute — показательный баг
- #1653 - monorepo support — архитектурная проблема
- #1967 - stub files — интеграция с экосистемой
Решите: Готовы ли вы к таким проблемам в обмен на скорость?
Самый важный совет
Не верьте статьям. Не верьте бенчмаркам. Не верьте даже Astral.
Откройте терминал и запустите
ty checkна своём коде.Цифры на графиках — это одно. Ваш проект — это реальность.
Полезные ссылки:
- ty Official Blog — официальный анонс Beta релиза
- ty Documentation — полная документация
- ty GitHub Repository — исходный код и issues
- ty Type System Guide — детали о пересечениях типов
- Talk Python To Me #506 — подкаст с Charlie Marsh (1 мая 2025)
- Charlie Marsh on X — твиты о ty
- Simon Willison's Analysis — разбор от известного разработчика
Реальные проблемы и обсуждения:
- GitHub Issues - ty — трекер ошибок (читайте перед миграцией!)
- Issue #398 — проблемы с неразрешенными атрибутами
- Issue #1653 — поддержка монорепо
- Issue #1967 — обнаружение заглушек типов
Sources:
- ty: An extremely fast Python type checker and LSP - Astral
- Python type checker ty now in beta | InfoWorld
- ty Documentation - Astral
- Type system features - ty Docs
- Talk Python Podcast Episode #506
- GitHub Issues - astral-sh/ty
- ty releases - GitHub
Делитесь опытом!
Попробовали ty? Столкнулись с проблемами? Нашли баги?
Пишите в комментариях или в Telegram. Обсудим, сравним, посмеёмся над багами.
Нужна помощь с миграцией на ty? Пишите на почту — помогу разобраться с edge cases и настроить CI.
Понравилась статья? Поделитесь с коллегой, который всё ещё ждёт, пока mypy проверит типы 5 минут в CI.
Подписывайтесь на обновления в Telegram — пишу про Python, инструменты и боль разработки. Без воды, только практика.
