Перейти к содержимому

ty от Astral: тайп-чекер, который переписывает правила игры

Константин Потапов
22 мин

Глубокий разбор ty — нового тайп-чекера от создателей uv и Ruff. Пересечения типов, интеллектуальное сужение, производительность в 10-100 раз быстрее mypy. Революция или очередной хайп?

Когда очередной инструмент меняет всё

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 это делает:

  1. Видит hasattr(being, "name")
  2. Анализирует каждый вариант в Union:
    • Person — всегда имеет name → остаётся как Person
    • Animal — может иметь через подклассы → создаёт Animal & HasName
    • None — это final тип, не может иметь атрибутов → исключается
  3. Результирующий тип: 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
ty2.19s1x (baseline)
Pyright19.62s9x медленнее
mypy45.66s21x медленнее

Вывод: ty проверяет home-assistant в 9 раз быстрее Pyright и в 21 раз быстрее mypy.

Тест 2: языковой сервер (LSP) в редакторе

Сценарий: Редактируем файл в PyTorch (огромная кодовая база), сохраняем изменение.

Задача: Инкрементальная проверка типов (только изменённый файл + зависимости).

LSP серверВремя откликаОтносительно ty
ty4.7ms1x (baseline)
Pyright386ms82x медленнее
Pyrefly2.38s506x медленнее

Вывод: 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 — типы для Django
  • sqlalchemy-stubs — типы для SQLAlchemy
  • pydantic-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})

Что изменилось:

  1. ty показал реальные проблемы вместо "Успех: проблем не найдено"
  2. Unknown безопаснее Any — ty предупреждает при доступе к атрибутам
  3. Постепенно добавляем аннотации → сразу видим эффект

Результат: За 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 интеграция

Установка расширения:

  1. Открыть VS Code Extensions (Cmd+Shift+X)
  2. Искать "ty"
  3. Установить "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 API45k25-30s0.3-0.5s~80-100x
FastAPI12k6-10s0.1-0.15s~60-80x
CLI tool3k2-3s0.04-0.06s~40-60x
ML service67k45-60s0.5-0.8s~70-100x
Monorepo180k3-4min2-3s~80-120x

Средний прирост: 60-100x ускорение (по официальным бенчмаркам Astral).

ty vs mypy vs Pyright: честное сравнение

Таблица возможностей

ФункцияtymypyPyright
Пересечения типов✅ Полноценные (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 check vs time 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 фокусируется на:

  1. Стабилизация API — финальный интерфейс конфигурации
  2. Python typing spec compliance — полное покрытие PEP 484, 526, 544, 585, 586, 593, 604, 612, 613, 647, 673, 675
  3. First-class Pydantic — нативная поддержка без костылей
  4. 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, приоритеты команды:

  1. Ближайшая цель: Поддержка ранних пользователей, стабилизация
  2. Stable релиз (2026): Полное покрытие Python typing спецификации
  3. 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 для mypy
  • library-ty-stubs для ty
  • library.pyi общие заглушки

Или ty будет совместим со всем? Пока неясно.

Риск 3: Скорость vs полнота проверки

ty быстрее, но проверяет ли он всё, что проверяет mypy за 10 лет развития? Первые Beta показывают "да", но время покажет.

Итоги: революция в Beta-оболочке

Революция.

ty — это не просто "ещё один быстрый тайп-чекер". Это переосмысление статического анализа Python с нуля.

Что меняется:

  1. Пересечения типов — прощайте, костыли с Union и приведениями
  2. Постепенная типизация — Unknown вместо Any, постепенная типизация работает
  3. Производительность — 10-100x ускорение, время CI с минут до секунд
  4. Достижимость — вывод типов вместо сопоставления с образцом

Для 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 час)

  1. Клонируйте тестовые примеры ty
  2. Запустите ty на своём самом сложном модуле
  3. Сравните результаты с mypy — что нашёл ty, что пропустил

3. Изучение проблем (15 минут)

Прочитайте самые обсуждаемые issues:

Решите: Готовы ли вы к таким проблемам в обмен на скорость?

Самый важный совет

Не верьте статьям. Не верьте бенчмаркам. Не верьте даже Astral.

Откройте терминал и запустите ty check на своём коде.

Цифры на графиках — это одно. Ваш проект — это реальность.


Полезные ссылки:

Реальные проблемы и обсуждения:

  • GitHub Issues - ty — трекер ошибок (читайте перед миграцией!)
  • Issue #398 — проблемы с неразрешенными атрибутами
  • Issue #1653 — поддержка монорепо
  • Issue #1967 — обнаружение заглушек типов

Sources:


Делитесь опытом!

Попробовали ty? Столкнулись с проблемами? Нашли баги?

Пишите в комментариях или в Telegram. Обсудим, сравним, посмеёмся над багами.

Нужна помощь с миграцией на ty? Пишите на почту — помогу разобраться с edge cases и настроить CI.

Понравилась статья? Поделитесь с коллегой, который всё ещё ждёт, пока mypy проверит типы 5 минут в CI.


Подписывайтесь на обновления в Telegram — пишу про Python, инструменты и боль разработки. Без воды, только практика.