Python: обзор языка, экосистема и дорожная карта курса
Этот трек — не про то, «как написать первый цикл». Основы программирования на Python уже разобраны в курсе «Программирование с нуля», и если вы ещё не различаете список и словарь, начните оттуда. Здесь мы говорим о другом: как язык устроен внутри, почему он ведёт себя именно так, и что нужно уметь, чтобы писать на нём код, который живёт в продакшене годами.
Разница принципиальная. Человек, знающий синтаксис Python, напишет работающий скрипт за
час. Человек, знающий модель исполнения Python, объяснит, почему этот скрипт съел 8 ГБ
памяти на проде, почему потоки не ускорили его в четыре раза, почему dataclass с
дефолтным списком развалился на второй итерации и почему замена цикла на numpy дала
ускорение в 60 раз, а замена for на map — ноль. Мы целимся во вторую категорию.
Язык, реализация и почему их важно различать
Первая вещь, ломающая интуицию людей из мира C# или Java: у Python нет отдельной формальной спецификации, которую реализуют конкурирующие вендоры. Есть Language Reference — документ, который довольно точно описывает семантику, — и есть CPython, эталонная реализация на C, которая де-факто и есть определение языка. Всё, что делает CPython, постепенно становится «тем, как работает Python».
Отсюда практическое следствие: часть свойств, которые вы принимаете за свойства языка, на самом деле свойства реализации.
| Свойство | Это язык или CPython? | Почему важно |
|---|---|---|
| Отступы вместо скобок | Язык | Работает везде |
Порядок вставки в dict сохраняется |
Язык с 3.7 (в 3.6 был деталью CPython) | Можно полагаться |
| Подсчёт ссылок, детерминированное освобождение | CPython | В PyPy файл закроется позже |
| GIL | CPython (и то не всегда с 3.13) | Определяет стратегию конкурентности |
Кеш малых int от −5 до 256 |
CPython | Ловушка с is |
Кроме CPython существуют PyPy (JIT-компиляция, чистый Python бывает быстрее в 5–20 раз), GraalPy на GraalVM, MicroPython для микроконтроллеров. В этом курсе, если не оговорено иное, речь про CPython 3.12+: это то, что стоит на 95 % серверов.
Модель исполнения: что происходит между файлом и результатом
Python часто называют «интерпретируемым», и это полуправда. На самом деле он компилируется в байт-код, а уже байт-код исполняется виртуальной машиной.
модуль ast"] AST -->|compile| BC["Байт-код
code object"] BC -.->|кеш на диск| PYC["__pycache__/*.pyc"] BC --> EVAL["Цикл ceval
стековая виртуальная машина"] EVAL --> OBJ["Операции над объектами
через слоты типа"] OBJ --> C["Вызовы C-кода
stdlib, numpy, сокеты, syscalls"]
Это не абстракция — байт-код можно посмотреть штатным модулем:
import dis
def add(a, b):
return a + b
dis.dis(add)
Вывод (Python 3.12; номера опкодов и их набор меняются между минорными версиями — это внутреннее API, не полагайтесь на него в коде):
2 0 RESUME 0
3 2 LOAD_FAST 0 (a)
4 LOAD_FAST 1 (b)
6 BINARY_OP 0 (+)
10 RETURN_VALUE
Три вывода из этой картинки, которые пригодятся весь курс:
- Стоимость операции — это стоимость опкода плюс стоимость диспетчеризации.
a + b— не сложение чисел, а поиск метода__add__у типаa, вызов, аллокация нового объекта-результата. Отсюда 30–100-кратное отставание от C на арифметике в цикле. - Быстрый Python — это Python, который делает мало опкодов. Вся практическая
оптимизация (см. https://courses.digitable.life/post/python/13-performance/) сводится к тому, чтобы цикл
уехал внутрь C-кода:
sum(),numpy,join, генераторы вместо ручных циклов. .pyc— это кеш компиляции, а не защита кода. Байт-код тривиально декомпилируется; «скомпилировать Python, чтобы скрыть исходник» — не работает.
Главная идея языка: всё есть объект, имя — это ярлык
Если из всего трека запомнить одно предложение, пусть будет это. В Python не бывает переменных в смысле C — ячеек памяти, куда кладут значение. Есть объекты в куче и имена, которые на них ссылаются.
a = [10, 20]
b = a # не копия! второй ярлык на тот же объект
b.append(30)
print(a) # [10, 20, 30]
print(a is b) # True — один и тот же объект
print(id(a) == id(b)) # True
b = [1] # присваивание перевешивает ярлык, объект не трогает
print(a) # [10, 20, 30]
Из этой модели растёт половина «странностей» Python — и половина его силы. Объектом является буквально всё: числа, функции, классы, модули, сами типы.
def greet(name: str) -> str:
return f"Привет, {name}!"
print(type(greet)) # <class 'function'>
greet.calls = 0 # функции — объекты, у них есть __dict__
print(type(int)) # <class 'type'> — класс тоже объект
print(type(type)) # <class 'type'> — и его тип тоже
handlers = {"ru": greet, "en": str.upper} # функции лежат в словарях как данные
print(handlers["ru"]("мир")) # Привет, мир!
Именно поэтому в Python не нужны паттерны вроде «Стратегия» в тяжёлом виде: функция первого класса уже есть стратегия. Подробный разбор — в https://courses.digitable.life/post/python/02-data-model/.
Протоколы вместо интерфейсов: почему len(x) работает на вашем классе
Второй столп языка — протоколы (dunder-методы). Встроенные функции и синтаксис не
знают о ваших типах ничего; они просто вызывают методы со специальными именами.
len(x) → type(x).__len__(x), x[i] → __getitem__, with x: → __enter__/__exit__,
for → __iter__. Это и есть знаменитая «утиная типизация», только формализованная.
class Deck:
"""Колода карт. Ничего не наследует, но ведёт себя как последовательность."""
def __init__(self, cards):
self._cards = list(cards)
def __len__(self):
return len(self._cards)
def __getitem__(self, i):
return self._cards[i]
deck = Deck(["A", "K", "Q"])
print(len(deck)) # 3 — работает __len__
print(deck[1]) # K — работает __getitem__
print("Q" in deck) # True — in откатывается на перебор через __getitem__
print(list(reversed(deck))) # ['Q', 'K', 'A'] — reversed использует __len__ + __getitem__
print(deck[::-1]) # ['Q', 'K', 'A'] — срез тоже приходит в __getitem__
Два метода — и объект бесплатно получил итерацию, срезы, оператор in, распаковку,
работу с random.choice. Это ключевой рычаг проектирования в Python: вы не наследуетесь
от базового класса, вы реализуете протокол.
С появлением typing.Protocol (PEP 544) эту неформальную договорённость можно
проверять статически — об этом в https://courses.digitable.life/post/python/07-typing/.
Философия: почему код на Python выглядит именно так
import this печатает «Дзен Python» (PEP 20) —
19 афоризмов Тима Питерса, которые реально определяют дизайн-решения языка. Ключевые:
- Readability counts. Код читают в десятки раз чаще, чем пишут. Отсюда отступы как
синтаксис,
and/or/notвместо&&/||/!, отсутствие тернарного?:в С-образном виде. - There should be one — and preferably only one — obvious way to do it. Прямая
противоположность девизу Perl. Это причина, по которой в языке нет
switchв С-стиле, аmatchпоявился только в 3.10 и с другой семантикой (структурное сопоставление, PEP 634). - Explicit is better than implicit. Отсюда обязательный
self, отсюда явныйimport, отсюда отсутствие неявных приведений"1" + 1. - Errors should never pass silently. Unless explicitly silenced. Отсюда культура
исключений и почему
except: pass— почти всегда баг (https://courses.digitable.life/post/python/08-errors-and-exceptions/). - Practicality beats purity. Отсюда GIL, отсюда изменяемые дефолты, отсюда
«we’re all consenting adults here»: приватность в Python — соглашение (
_name), а не запрет компилятора.
Отдельно стоит EAFP — Easier to Ask Forgiveness than Permission. В Python принято пробовать и ловить исключение, а не проверять условия заранее (LBYL):
# LBYL — «посмотри перед прыжком», стиль из C/Java
if "key" in config and config["key"] is not None:
value = config["key"]
# EAFP — идиоматичный Python: нет гонки между проверкой и использованием
try:
value = config["key"]
except KeyError:
value = DEFAULT
EAFP не просто вкусовщина: между if os.path.exists(p) и open(p) файл может исчезнуть,
а между if key in d и d[key] другой поток может удалить ключ. Проверка и действие не
атомарны — исключение атомарно.
Как выглядит современный идиоматичный Python
Чтобы задать планку на весь курс, вот законченная утилита. Она короткая, но в ней
собрано почти всё, что мы будем разбирать: аннотации типов, dataclass, pathlib,
генераторные выражения, collections, форматирование, корректный выход из процесса.
"""Топ-N самых частых слов в текстовом файле."""
from __future__ import annotations
import argparse
import re
import sys
from collections import Counter
from dataclasses import dataclass
from pathlib import Path
WORD_RE = re.compile(r"\w+", re.UNICODE)
@dataclass(frozen=True, slots=True)
class WordStat:
"""Неизменяемая запись: frozen даёт хешируемость, slots — экономию памяти."""
word: str
count: int
share: float
def read_words(path: Path) -> list[str]:
# pathlib вместо конкатенации строк и open/close вручную
text = path.read_text(encoding="utf-8")
return [m.group(0).lower() for m in WORD_RE.finditer(text)]
def top_words(words: list[str], n: int) -> list[WordStat]:
counter = Counter(words) # C-реализация, быстрее ручного dict
total = sum(counter.values()) or 1 # защита от деления на ноль
return [WordStat(w, c, c / total) for w, c in counter.most_common(n)]
def main() -> int:
parser = argparse.ArgumentParser(description="Топ слов в файле")
parser.add_argument("path", type=Path, help="путь к текстовому файлу")
parser.add_argument("-n", "--top", type=int, default=10, help="сколько слов вывести")
args = parser.parse_args()
try:
words = read_words(args.path)
except FileNotFoundError:
print(f"Файл не найден: {args.path}", file=sys.stderr)
return 1
except UnicodeDecodeError:
print(f"Не похоже на текст в UTF-8: {args.path}", file=sys.stderr)
return 2
for stat in top_words(words, args.top):
print(f"{stat.word:<15} {stat.count:>6} {stat.share:>7.2%}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Запуск: python top_words.py article.txt -n 5. Вывод:
и 412 4.11%
в 388 3.87%
python 201 2.01%
на 177 1.77%
код 95 0.95%
Обратите внимание на то, чего здесь нет: ручного открытия/закрытия файла, индексных
циклов, str(x) + " " + str(y), класса-обёртки ради одного метода, глобального состояния.
Это и есть «питоничность» — предмет статьи https://courses.digitable.life/post/python/06-pythonic-idioms/.
Грабли, на которых спотыкаются все
Это не курьёзы, а следствия модели данных. Каждый пункт мы разберём подробно позже, но увидеть их стоит сразу.
1. Изменяемый аргумент по умолчанию. Дефолт вычисляется один раз — при создании функции, а не при каждом вызове.
def append_to(item, target=[]): # ← так писать нельзя
target.append(item)
return target
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] ← тот же самый список!
def append_ok(item, target=None): # ← правильно
target = [] if target is None else target
target.append(item)
return target
2. Позднее связывание в замыканиях. Замыкание захватывает переменную, а не значение.
fns = [lambda: i for i in range(3)]
print([f() for f in fns]) # [2, 2, 2], а не [0, 1, 2]
fns = [lambda i=i: i for i in range(3)] # приём: зафиксировать через дефолт
print([f() for f in fns]) # [0, 1, 2]
3. Умножение списка списков. * копирует ссылку, а не содержимое.
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid) # [[1, 0, 0], [1, 0, 0], [1, 0, 0]] — три ярлыка на одну строку
grid = [[0] * 3 for _ in range(3)] # правильно: три разных списка
4. is вместо ==. is сравнивает идентичность объектов. Из-за кеша малых чисел
и интернирования строк это иногда «работает» — и тем опаснее.
a, b = 256, 256
print(a is b) # True — кеш малых int в CPython
a, b = 257, 257
print(a is b) # в скрипте True (константы кода), в REPL построчно False
Правило: is — только для None, True, False и синглтонов-сентинелов.
5. Изменение коллекции во время итерации.
xs = [1, 2, 2, 3]
for x in xs:
if x == 2:
xs.remove(x)
print(xs) # [1, 2, 3] — одна двойка выжила: индексы сдвинулись под итератором
6. or для дефолтов ломается на «ложных» значениях.
def connect(timeout=None):
timeout = timeout or 30 # timeout=0 молча превратится в 30
timeout = 30 if timeout is None else timeout # правильно
7. Файл, затеняющий стандартный модуль. Создали в проекте random.py, email.py
или types.py — и import random тянет ваш файл. Ошибка выглядит абсолютно
мистически. Подробнее про механику импортов — https://courses.digitable.life/post/python/09-modules-and-packaging/.
8. Числа с плавающей точкой. Это не баг Python, а IEEE 754, но всплывает именно тут:
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
from decimal import Decimal
print(Decimal("0.1") + Decimal("0.2") == Decimal("0.3")) # True — для денег только так
Где Python выигрывает, а где проигрывает
Честный разговор о границах. Python — язык-клей и язык-прототип, который вырос в промышленный инструмент, но не стал универсальным.
Сильные стороны:
- Скорость от идеи до работающего кода. На типовой задаче кода в 2–4 раза меньше, чем на Java или Go. Для исследовательских и продуктовых гипотез это решает.
- Экосистема данных и ML. numpy, pandas, polars, scikit-learn, PyTorch, JAX — это не «библиотеки для Python», это де-факто индустриальный стандарт, у которого просто нет полноценной альтернативы в других языках (https://courses.digitable.life/post/machine-learning/00-overview/).
- Роль языка-клея. Python отлично склеивает C, Rust, СУБД, HTTP-сервисы и очереди. Тяжёлая работа уходит в нативный код, Python оркестрирует.
- Читаемость и низкий порог входа. Код на Python читает аналитик, DevOps и ML-инженер — это редкое свойство, и оно определяет выбор в смешанных командах.
- Стандартная библиотека.
itertools,collections,dataclasses,pathlib,sqlite3,logging,argparse,asyncio— огромный объём готового качественного кода (https://courses.digitable.life/post/python/10-stdlib/).
Слабые стороны — и это надо знать до, а не после:
- Скорость чистого Python. Тесный цикл на 10 млн итераций: чистый Python — порядка
0,5–1 с,
sum(range(...))— около 0,1 с, numpy — единицы миллисекунд, C — доли миллисекунды. Разрыв в 30–100 раз никуда не денется. - Параллелизм по CPU. GIL не даёт двум потокам одновременно исполнять байт-код.
Обходы есть (
multiprocessing, нативные расширения, с 3.13 — экспериментальные сборки без GIL), но это осознанная работа, а не «просто запусти потоки» (https://courses.digitable.life/post/python/11-concurrency/). - Память. Каждый
int— отдельный объект на 28 байт, список из миллиона чисел — десятки мегабайт вместо 8 МБ массива. Для больших данных нужен numpy/pandas/Arrow (https://courses.digitable.life/post/python/14-data-stack/). - Дистрибуция приложений конечному пользователю. Собрать один надёжный бинарник, как в Go или Rust, тяжело: PyInstaller/Nuitka работают, но это не «go build».
- Мобильная и десктопная разработка. Формально возможна (Kivy, BeeWare, официальная tier-3-поддержка iOS/Android с 3.13), фактически — маргинальна.
- Рефакторинг больших кодовых баз без типов. Динамика мстит на 100 тыс. строк. Лечится строгим mypy с первого дня — и это не опция, а требование (https://courses.digitable.life/post/python/07-typing/).
Куда Python тащить не надо: ядро торгового или игрового движка, драйверы, прошивки с жёстким реальным временем, CLI-утилиты, где важен старт за 5 мс (интерпретатор стартует 20–50 мс), библиотеки, которые должны потреблять чужие экосистемы (JVM, .NET). Для таких задач посмотрите на https://courses.digitable.life/post/golang/00-overview/ или https://courses.digitable.life/post/csharp/00-overview/.
Экосистема: что на чём стоит
Эта картинка объясняет главный производственный приём Python: проблема производительности решается спуском на слой ниже. Медленный цикл на pandas → векторная операция numpy → при необходимости расширение на Cython или Rust. Переписывать верхний слой «умнее» бесполезно.
Пара цифр о масштабе: на PyPI более 600 тысяч пакетов, и это одновременно сила и риск — вопрос доверия к зависимостям мы разберём в https://courses.digitable.life/post/python/09-modules-and-packaging/ и https://courses.digitable.life/post/python/17-deploy-and-resources/.
Инструментальный слой за последние годы пережил смену поколений: pip + virtualenv +
flake8 + black + isort во многих командах заменились на
uv и ruff — оба написаны на
Rust и быстрее предшественников на порядки. Это разберём сразу в следующей статье.
Версии: что живо, что мертво, куда всё идёт
Практические правила на 2026 год:
- Новый проект начинайте на последней стабильной версии (3.14) либо на предыдущей (3.13), если ключевые зависимости ещё не собрали колёса.
- Версии живут пять лет: примерно два года багфиксов и три года security-фиксов (PEP 602). Актуальный статус — devguide.python.org/versions.
- Python 2 мёртв. Если вам достался код на нём — это задача миграции, а не разработки.
- Free-threading (сборка без GIL) — самое серьёзное изменение рантайма за 20 лет, но переход займёт годы: нужна пересборка всех C-расширений. Планируйте архитектуру, исходя из наличия GIL, и радуйтесь, когда его не будет.
Конкурентность: короткая версия ответа
Вопрос «а как в Python с многопоточностью» возникает у всех сразу, поэтому дадим карту заранее. Полный разбор — в https://courses.digitable.life/post/python/11-concurrency/.
| Что за нагрузка | Инструмент | Почему |
|---|---|---|
| Тысячи сетевых запросов | asyncio |
Один поток, ожидание не блокирует; масштаб до десятков тысяч соединений |
| Десятки блокирующих вызовов (файлы, legacy-драйверы БД) | threading / ThreadPoolExecutor |
GIL отпускается на I/O, потоки реально помогают |
| Тяжёлые вычисления на всех ядрах | multiprocessing / ProcessPoolExecutor |
Обход GIL через отдельные процессы, ценой сериализации данных |
| Матрицы, массивы, ML | numpy / PyTorch | Вычисления уходят в C/BLAS, GIL отпускается, потоки внутри библиотеки |
| Веб-сервер | несколько воркеров процессов + async внутри | Комбинация обоих подходов |
Ключевая мысль: GIL мешает только параллельному исполнению байт-кода. На I/O он не мешает вообще, на нативных вычислениях — почти не мешает. Большинство «медленно из-за GIL» на практике оказывается «медленно, потому что цикл на чистом Python».
Дорожная карта трека
Статьи выстроены лестницей — каждая опирается на предыдущие. Если вы опытный инженер из другого языка, всё равно не пропускайте 02 и 06: именно там прячется специфика, на которой спотыкаются сеньоры.
- Установка и инструментарий — версии и pyenv, venv, pip против uv и poetry, ruff, mypy, pre-commit, воспроизводимое окружение.
- Модель данных — объекты, ссылки, изменяемость,
хешируемость,
__eq__/__hash__, dunder-методы, подсчёт ссылок и сборка мусора. - Коллекции — устройство
list,dict,set,tuple, сложность операций,collectionsиheapq, выбор структуры под задачу. - Функции и области видимости — LEGB,
аргументы всех видов, замыкания и ячейки, декораторы,
functools, частичное применение. - ООП в Python — классы и атрибуты, MRO и C3-линеаризация,
super(), дескрипторы,property,__slots__,dataclasses, метаклассы. - Идиоматичный Python — итераторы и генераторы,
ленивые пайплайны,
yield from, контекстные менеджеры, comprehensions,itertools. - Аннотации типов — mypy в строгом режиме,
Protocol, дженерики PEP 695,TypedDict,Literal,Self, типизация чужого кода. - Исключения — иерархия, свои классы
ошибок, EAFP,
contextlib.suppress,ExceptionGroup, цепочки, ретраи. - Модули и пакетирование — механика
импортов, циклические зависимости,
pyproject.toml, wheels, публикация на PyPI. - Стандартная библиотека — карта модулей, которые
экономят зависимости:
pathlib,datetime,json,sqlite3,logging,subprocess. - Конкурентность — GIL изнутри, threading, multiprocessing, asyncio и event loop, структурная конкурентность, типичные дедлоки.
- Тестирование — pytest, фикстуры и их области, параметризация, моки без боли, property-based через Hypothesis, покрытие (см. также https://courses.digitable.life/post/testing/00-overview/).
- Производительность — профилирование по-настоящему
(
cProfile,py-spy,memray), алгоритмы против микрооптимизаций, Cython, Rust, PyPy. - Работа с данными — numpy и векторизация, pandas и его ловушки, polars, Parquet и Arrow, память и типы колонок.
- Веб и API — FastAPI и Pydantic, Django и ORM, WSGI против ASGI, валидация, аутентификация, фоновые задачи.
- Продакшн-архитектура — слои и порты, внедрение зависимостей без фреймворка, конфигурация, структурное логирование.
- Деплой и ресурсы — Docker и multi-stage, healthchecks, OpenTelemetry, безопасность зависимостей, CI/CD и курированный список источников.
Сквозной пример трека
Чтобы теория не висела в воздухе, через статьи проходит один сервис — агрегатор событий: он принимает поток событий по HTTP, валидирует, складывает в хранилище и отдаёт агрегаты. Мы будем возвращаться к нему в разных разрезах:
- в «Модели данных» — как представить событие, чтобы его можно было класть в
setи сравнивать; - в «Коллекциях» — какую структуру взять под окно агрегации;
- в «Идиоматике» — как построить ленивый пайплайн разбора файла на 20 ГБ;
- в «Типах» — как описать контракт хранилища через
Protocolи подменять реализацию; - в «Конкурентности» — как читать сотни источников без потоков;
- в «Тестировании» — как покрыть его юнитами, интеграцией и property-based тестами;
- в «Архитектуре» и «Деплое» — как разложить по слоям, собрать образ и наблюдать в проде.
Как учиться, чтобы уметь применять
- Держите открытый REPL. Это главное преимущество Python перед компилируемыми
языками: гипотеза проверяется за 5 секунд.
python -i script.pyбросает вас в интерактив с уже загруженным состоянием. - Читайте исходники стандартной библиотеки. Они на Python и лежат прямо у вас:
python -c "import dataclasses, inspect; print(inspect.getsourcefile(dataclasses))". Кодdataclasses,enum,contextlib,functools— образец идиоматики. - Смотрите под капот.
dis,sys.getsizeof,id,gc.get_referrers,timeit— не экзотика, а повседневные инструменты для проверки «а как это на самом деле». - Включайте строгие инструменты с первого дня.
ruff check,mypy --strict,pytest -W error. Динамический язык требует внешней дисциплины — пусть её обеспечивают машины, а не сила воли. - Читайте PEP. peps.python.org — это не бюрократия, а лучшая документация «почему так»: каждый PEP содержит мотивацию, отвергнутые альтернативы и обоснование.
- Не читайте случайные ответы со Stack Overflow пятилетней давности. Python быстро
меняется:
%-форматирование →.format()→ f-строки;os.path→pathlib;setup.py→pyproject.toml. Всегда сверяйтесь с текущей документацией.
Что вы будете уметь в конце
- Объяснять поведение любого куска Python-кода через модель данных, а не через магию.
- Проектировать API классов через протоколы и писать код, который естественно встраивается в язык.
- Держать строгую типизацию в проекте на сотни тысяч строк и рефакторить без страха.
- Осознанно выбирать между asyncio, потоками и процессами, понимая, что делает GIL.
- Находить настоящее узкое место профилировщиком и ускорять код в десятки раз.
- Собирать, тестировать, паковать и деплоить сервис, за которым можно наблюдать в проде.
Источники, к которым стоит возвращаться
- docs.python.org — официальная документация; отдельно Language Reference как описание семантики.
- peps.python.org — все PEP: мотивация и обоснование решений.
- devguide.python.org — как устроен сам CPython.
- Лучано Рамальо, «Fluent Python», 2-е издание — лучшая книга о модели данных и идиоматике (оф. страница).
- Дэвид Бизли, Брайан Джонс, «Python Cookbook», 3-е издание — рецепты с объяснениями; курсы и материалы Бизли о GIL и генераторах.
- Бретт Слаткин, «Effective Python», 2-е издание — 90 конкретных правил (effectivepython.com).
- Anthony Shaw, «CPython Internals» — устройство интерпретатора изнутри (realpython.com/products/cpython-internals-book).
- Real Python — качественные разборы прикладных тем.
- Блог Ned Batchelder и его классический доклад «Facts and Myths about Python names and values».
Что дальше
Установка и инструментарий: версии, venv, pip, uv, poetry, линтеры — соберём рабочее окружение так, как это делают в 2026 году: управление версиями интерпретатора, изолированные окружения, воспроизводимые зависимости, ruff и mypy в pre-commit. Без этого фундамента всё остальное превращается в «у меня на машине работало».