ПЕП: https://peps.python.org/pep-0800
Обсуждение: https://discuss.python.org/t/99910
Реализация в
typing_extensions: https://github.com/python/typing_extensions/blob/a7610ef567132cac2b5319fa193c830b655364c6/src/typing_extensions.py#L358Когда я критикую систему типов в питоне, говоря, что её не продумывали заранее, то
disjoint_base - отличный пример моих слов.В питоне можно наследоваться от нескольких классов (и вообще всего с методом
__mro_entries__, да). Что иногда довольно удобно, если использовать без фанатизма. Но проблема в том, что не все наборы классов подходят для множественного наследования.Например:
>>> class What(str, int): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Почему такое происходит почитать можно тут. Но, нам важно узнать, что в CPython есть концепция "solid base", которая считается для всех созданных классов вот тут. Если очень кратко, то CPython должен уметь построить правильный memory-layout для всех классов потомков. Например, все потомки
int должны быть вот так уложены в памяти:
typedef struct _PyLongValue {
uintptr_t lv_tag; /* Number of digits, sign and flags */
digit ob_digit[1];
} _PyLongValue;
struct _longobject {
PyObject_HEAD
_PyLongValue long_value;
};
Иначе - перестанет работать базовая логика
int. А str устроены по-другому.
/* Object format for Unicode subclasses. */
typedef struct {
PyCompactUnicodeObject _base;
union {
void *any;
Py_UCS1 *latin1;
Py_UCS2 *ucs2;
Py_UCS4 *ucs4;
} data; /* Canonical, smallest-form Unicode buffer */
} PyUnicodeObject;
Сделать область памяти для потомка
int и str сразу - невозможно. Потому и выкидывается ошибка.Но, сделать так можно и со своими классами, не обязательно использовать C, достаточно конфликта в
__slots__:
>>> class A:
... __slots__ = ('a',)
>>> class B:
... __slots__ = ('b',)
>>> class C(A, B): ...
Traceback (most recent call last):
TypeError: multiple bases have instance lay-out conflict
Подробности из ПЕПа про "solid bases".
Типизация
Теперь
int, str и многие другие классы в typeshed помечены как @disjoint_base типы. Значит, что только один такой класс может быть в mro. Раньше такое костылили все тайпчекеры по-своему.Как следствие, тайпчекеры теперь более четко смогут находить и другие проблемы. Например, в местах где мы создаем "временный" тип:
def g(x: int):
match x:
case str(): # unreachable
print("It's both!")
Раньше такой код проходил в некоторых тайпчекерах. Ведь они думали, что тип подкласс
int и str может существовать. А теперь - будут знать, что такое невозможно на уровне определения и выкидывать правильную ошибку.Отличный ПЕП, система типов стала чуть лучше.
Обсуждение: знали ли вы про solid и disjoint bases в питоне? Стреляли ли себе в ногу таким?
| Поддержать | YouTube | GitHub | Чат |