Подсчёт ссылок
В CPython у каждого объекта есть счётчик ссылок. Когда он падает до нуля, объект освобождается сразу — поэтому файл, на который больше нет ссылок, закрывается в тот же момент. sys.getrefcount(x) покажет счётчик (на единицу больше — сам аргумент).
Циклические ссылки — a ссылается на b, b на a — счётчик до нуля не доведут. Их находит циклический сборщик модуля gc, который работает по поколениям: молодые объекты проверяются чаще. В цикле момент вызова __del__ непредсказуем, поэтому ресурсы закрывают через with.
Слабые ссылки
Слабая ссылка не увеличивает счётчик: объект удаляется, когда исчезнут обычные ссылки, а слабая начинает возвращать None.
import weakref
cache = weakref.WeakValueDictionary()
Применения — кэши и реестры, которые не должны продлевать жизнь объектов, разрыв циклов «родитель ↔ ребёнок», наблюдатели. weakref.finalize — надёжная замена __del__. Не у всех типов есть слабые ссылки: у int, str, tuple, list их нет.
Почему a is b иногда True
CPython заранее создаёт целые от −5 до 256 и переиспользует их. Строки, похожие на идентификаторы, и одинаковые константы в одном блоке кода интернируются. Поэтому a = 256; b = 256; a is b — True, а с 257 результат зависит от того, как выполнялся код. Это детали реализации: значения сравнивают через ==.
Копирование
b = a— не копия, а второе имя.- Поверхностная копия — новый контейнер со старыми элементами:
copy.copy(x),x[:],list(x),x.copy(). - Глубокая — копирует всё рекурсивно:
copy.deepcopy(x).
grid = [[0] * 3] * 3 # три ссылки на один и тот же список
grid[0][0] = 1 # меняется «каждая» строка
grid = [[0] * 3 for _ in range(3)] # правильно
Сложность операций
| Операция | list | dict, set |
|---|---|---|
| доступ по индексу или ключу | O(1) | O(1) в среднем |
append, add | O(1) амортизированно | O(1) |
| вставка в начало | O(n) | — |
x in ... | O(n) | O(1) |
x in list внутри цикла — самая частая причина неожиданного O(n²). Очередь с двух концов — deque, сортировка — O(n log n).
Сначала измерить
- cProfile (и snakeviz для картинки) — время по функциям локально.
- py-spy — сэмплирующий профилировщик, подключается к работающему процессу без перезапуска;
py-spy dumpпоказывает стеки зависшего процесса. - line_profiler — построчно; tracemalloc, memray — память.
timeit— для микроизмерений.
Профилируют под реалистичной нагрузкой: узким местом часто оказывается ввод-вывод, а не процессор.
Почему Python медленный и что делать
Каждая операция проверяет типы, числа — объекты в куче. Ускоряют так:
- алгоритм и правильные структуры данных — обычно самый большой выигрыш;
- встроенные функции на C вместо ручных циклов:
sum,sorted,str.join, множества; - векторизация — NumPy и pandas выполняют циклы на C;
- расширения на Cython или Rust, Numba для числовых функций, PyPy;
- процессы — для параллельных вычислений.
Python 3.11 стал быстрее на 10–60 % за счёт адаптивного интерпретатора.
Что запомнить
- Объект освобождается, когда счётчик ссылок падает до нуля; циклы ловит gc.
- Слабые ссылки — для кэшей, которые не держат объекты.
isдля чисел и строк — деталь реализации.inпо списку в цикле — O(n²).- Сначала профиль, потом оптимизация.