История одного PR: https://github.com/python/cpython/pull/124795
Как вы знаете, в PEP695 (https://peps.python.org/pep-0695/) были добавлены новые тайпалиасы, которые работают поверх нового синтаксиса:
type ResultE[T] = Result[T, Exception]У него довольно много граничений. Например:
- Нельзя использовать дубликаты в именах параметров:
>>> type A[T, T] = ...
SyntaxError: duplicate type parameter 'T'
- Нельзя использовать литералы вместо имен:
>>> type A[1] = ...
SyntaxError: invalid syntax
- С Python3.13 нельзя использовать типовые параметры без дефолта после параметра с дефолтами
>>> type A[T=int, S] = ...
SyntaxError: non-default type parameter 'S' follows default type parameter
Сколько на самом деле тайпалиасов?
Однако, все не так просто. Ведь на самом деле синтаксис
type просто создает обычный рантайм объект типа typing.TypeAliasType. И его можно создать руками.
>>> type A[T] = list[T]
>>> type(A)
<class 'typing.TypeAliasType'>
>>> A.__type_params__
(T,)
>>> A.__value__
list[T]
Сравните с "ручным" способом (они почти идентичны, кроме ленивости вычисления `list[T]`):
>>> from typing import TypeAliasType, TypeVar
>>> T = TypeVar('T', infer_variance=True)
>>> A = TypeAliasType('A', list[T], type_params=(T,))
>>> type(A)
<class 'typing.TypeAliasType'>
>>> A.__type_params__
(T,)
>>> A.__value__
list[T]
Так вот 🌚️️️️
У меня еще не все. Есть еще один
TypeAliasType 😱В
typing_extensions для портирования поддержки библиотеками, кто инспектирует аннотации на 3.11 и ниже.Вот он: https://github.com/python/typing_extensions/blob/17d3a37635bad3902c4e913a48d969cbebfb08c3/src/typing_extensions.py#L3503
Проблемы в реализации
Пользователь нашел проблему, что при ручном создании
TypeAliasType не было никаких проверок. Почему? потому что все проверки были не в самом коде объекта, а на уровне парсера / компилятора. Руками туда можно было отправиль что угодно! И в C версию, и в Python версию.Теперь проверки есть, например вот так мы проверяем, что типовые параметры без дефолта не идут после параметров с дефолтами:
for (Py_ssize_t index = 0; index < length; index++) {
PyObject *type_param = PyTuple_GET_ITEM(type_params, index);
PyObject *dflt = get_type_param_default(ts, type_param);
if (dflt == NULL) {
*err = 1;
return NULL;
}
if (dflt == &_Py_NoDefaultStruct) {
if (default_seen) {
*err = 1;
PyErr_Format(PyExc_TypeError,
"non-default type parameter '%R' "
"follows default type parameter",
type_param);
return NULL;
}
} else {
default_seen = 1;
Py_DECREF(dflt);
}
}
Практическая ценность
Кстати, такие тайпалиасы уже работают! Смотрите на
--enable-incomplete-feature=NewGenericSyntax в mypy:
type A[T] = list[T]
def get_first[T](arg: A[T]) -> T:
return arg[0]
reveal_type(get_first([1, 2, 3]))
https://mypy-play.net/?mypy=latest&python=3.12&enable-incomplete-feature=NewGenericSyntax&flags=strict&gist=7b33597fb3f0cb723ac816efc2e2caef
Как вы используете type alias'ы в своем коде?
Какой вид предпочитаете:
TypeAlias, TypeAliasType, ключевое слово type?