Модель данных Python
Зачем знать модель данных
Большинство Python-разработчиков пишут код, не задумываясь о том, как он устроен внутри. Для прикладных задач этого достаточно — до тех пор, пока не придётся отлаживать утечку памяти, разбираться, почему is ведёт себя не так, как ==, или объяснять на собеседовании, чем кортеж быстрее списка.
Модель данных Python — это набор правил, по которым интерпретатор создаёт, хранит и уничтожает объекты. Понимание этих правил позволяет:
- Писать библиотеки — любой dunder-метод (
__init__,__hash__,__repr__) это часть модели данных - Оптимизировать — зная, как устроен dict или list, можно предсказать производительность
- Отлаживать — утечки памяти, неожиданные мутации, странное поведение
is— всё объясняется моделью данных - Проходить собеседования — вопросы про mutable default arguments, кэширование int и GC задают постоянно
Официальная спецификация: Data Model — Python docs.
Реализации Python
Прежде чем говорить о внутреннем устройстве, важно понимать: Python — это спецификация языка, а не конкретная программа. Существует несколько реализаций:
| Реализация | Язык | Особенности |
|---|---|---|
| CPython | C | Эталонная реализация, самая распространённая |
| PyPy | RPython | JIT-компиляция, в 4–10× быстрее CPython на вычислениях |
| Jython | Java | Работает на JVM, доступ к Java-библиотекам |
| IronPython | C# | Работает на .NET CLR |
| Cython | C/Python | Компилирует Python-подобный код в C-расширения |
В этом курсе, когда мы говорим «Python», мы имеем в виду CPython — именно его устанавливают через python.org и именно его детали реализации (кэширование int, подсчёт ссылок, GIL) мы будем разбирать. Некоторые вещи, которые мы обсуждаем (например, диапазон кэширования целых чисел) — это детали реализации CPython, а не часть спецификации языка. В других реализациях они могут отличаться.
Интроспекция
Python позволяет программе исследовать и изменять саму себя во время выполнения. Эта возможность называется интроспекцией (в Java-мире используется термин «рефлексия», в Python-сообществе принято говорить «интроспекция»):
- Можно узнать тип переменной «на лету» —
type(x),isinstance(x, int) - Можно посмотреть структуру объекта —
dir(obj),vars(obj) - Можно исполнить код, сгенерированный в рантайме —
eval(),exec() - Можно получить доступ к AST программы из самой программы
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(1, 2)
print(type(p)) # <class '__main__.Point'>
print(dir(p)) # ['__class__', ..., 'x', 'y']
print(hasattr(p, 'x')) # True
print(vars(p)) # {'x': 1, 'y': 2}
Благодаря интроспекции работают такие инструменты, как pytest (автоматическое обнаружение тестов), Sphinx (генерация документации из docstring) и IPython (автодополнение).
Типы данных и динамическая типизация
Python использует динамическую типизацию — тип переменной определяется во время выполнения, а не при компиляции. Одна и та же переменная может последовательно ссылаться на объекты разных типов:
x = 1
print(x, type(x)) # 1 <class 'int'>
x = x * 1.5
print(x, type(x)) # 1.5 <class 'float'>
x = str(x)
print(x, type(x)) # 1.5 <class 'str'>
print(type(type(x))) # <class 'type'>
Привести один тип к другому можно с помощью встроенных функций: int(), str(), float(), list(), tuple() и т.д.
Утиная типизация (duck typing): «Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка». Python не проверяет тип объекта — он проверяет, поддерживает ли объект нужный протокол (набор методов). Если вы передаёте объект в len(), Python не спрашивает «это list?» — он вызывает obj.__len__(). Есть метод — значит, подходит.