Важно: Эксперимент нужно проводить именно в стандартной интерактивной оболочке Python (запускается командой python в терминале). Если вы пишете этот код в Jupyter Notebook, Google Colab или PyCharm, результат может отличаться, так как эти среды воспринимают ячейку кода как единый блок (об этом ниже).
Примеры в статье актуальны для CPython версий 3.8 — 3.11. В других реализациях (PyPy) или будущих версиях поведение оптимизатора может измениться.
Python часто называют языком с низким порогом входа: написал код — и всё работает. Мы привыкли, что интерпретатор берет на себя всю грязную работу: управление памятью, сборку мусора и аллокацию объектов. Но иногда эта «магия» под капотом выдает результаты, которые ставят в тупик даже тех, кто пишет на Python уже не первый месяц.
Давайте проведем простой эксперимент. Откройте консоль интерпретатора (REPL) и введите следующий код:
>>> a = 256
>>> b = 256
>>> a is b
True
Пока всё логично. Переменные ссылаются на одно и то же число, поэтому оператор is (который проверяет идентичность объектов, а не просто равенство значений) возвращает True.
А теперь увеличим ставки всего на единицу:
>>> a = 257
>>> b = 257
>>> a is b
False
Стоп, что? Почему для числа 256 Python использует один и тот же объект в памяти, а для 257 создает два разных? Неужели математика сломалась?
Спойлер: с математикой всё в порядке. Мы столкнулись с механизмом оптимизации CPython, который называется интернирование (или кеширование) малых целых чисел. Сегодня разберемся, как именно это работает, почему выбраны именно такие границы и почему использование is для сравнения чисел — это лотерея, в которую лучше не играть.
Разбор полетов: что на самом деле сравнивает is?
Прежде чем лезть в исходный код CPython, давайте разберемся с базой. Чтобы понять магию чисел, нужно вспомнить фундаментальное правило Python: переменная — это не коробка, в которой лежит значение. Это стикер (ссылка), который мы лепим на объект в памяти.
Когда вы пишете a = 257, происходит следующее:
Python создает в памяти объект типа
intсо значением257.Переменная
aстановится ссылкой на этот объект.
Если следом написать b = 257, Python (по умолчанию) честно создаст второй объект в другом участке памяти и повесит на него ярлык b. У нас получается два разных объекта с одинаковым содержимым.
В игру вступает id()
У каждого объекта в Python есть уникальный идентификатор. В стандартной реализации (CPython) этот идентификатор — прямой адрес объекта в оперативной памяти. Узнать его можно с помощью встроенной функции id().
Давайте проверим наши переменные из введения:
>>> a = 257
>>> b = 257
>>> id(a)
140702123456784 # Адрес первого объекта
>>> id(b)
140702123456816 # Адрес второго объекта. Он другой!
is против ==
Вот тут многие новички и попадают в ловушку.
Оператор
==сравнивает значения. Ему важно содержимое: «Равны ли числа внутри этих объектов?». В нашем случае, поэтому
a == bвернетTrue.Оператор
isсравнивает адреса памяти (identity). Он проверяет, является лиaиbссылками на один и тот же объект физически. Грубо говоря,a is b— это просто более красивый способ написатьid(a) == id(b).
Возвращаясь к нашему примеру с числом 256:
>>> a = 256
>>> b = 256
>>> id(a)
140702123456752
>>> id(b)
140702123456752 # Адреса совпали!
Здесь is возвращает True, потому что ссылки a и b указывают на один и тот же кусок памяти. Но почему Python решил сэкономить память на числе 256, но "пожадничал" для 257?
Секрет числа 256: Small Integer Caching
Разгадка кроется в том, как CPython (стандартная реализация языка, на которой мы все сидим) подходит к оптимизации. Создание нового объекта — это всегда расходы: нужно выделить память, инициализировать счетчик ссылок, настроить тип. Если делать это для каждого числа в цикле, производительность просядет.
Разработчики Python проанализировали, какие числа используются в коде чаще всего. Это индексы массивов, счетчики циклов, коды возврата (-1), булевы значения (0 и 1).
Диапазон «Бессмертных»
В исходном коде CPython (файл Objects/longobject.c) есть механизм, называемый Small Integer Caching.
При запуске интерпретатора Python заранее создает массив объектов для целых чисел в диапазоне от -5 до 256 (включительно). Эти числа загружаются в память еще до того, как вы напишете первую строчку кода, и остаются там до завершения процесса.
Когда вы пишете
a = 5, Python не создает новый объект. Он просто отдает вам ссылку на уже существующий, «закешированный» объект5.Когда вы пишете
a = 256, вы получаете ссылку на последний элемент этого кеша.Но как только вы запрашиваете
a = 257, вы выходите за пределы кешированного массива. Python вынужден честно выделить новую память под этот объект.
Почему именно от -5 до 256?
Нижняя граница (-5): Отрицательные числа используются редко, но часто нужны для индексации с конца списка (например,
list[-1]). Диапазон до -5 покрывает самые частые случаи.Верхняя граница (256): Это число не случайное. 256 — это
. Этот диапазон покрывает все возможные значения байта (0–255), а также подавляющее большинство счетчиков в циклах и длин массивов.
Расширять этот диапазон дальше посчитали нецелесообразным: это увеличило бы потребление памяти на старте программы ради чисел, которые используются не так часто.
Проверяем границы
Убедиться в этом легко. Давайте «пощупаем» границы этого массива:
# Нижняя граница
>>> a = -5
>>> b = -5
>>> a is b
True # Входит в кеш
>>> a = -6
>>> b = -6
>>> a is b
False # Уже создаются новые объекты
# Верхняя граница
>>> a = 256
>>> b = 256
>>> a is b
True # Всё ещё кеш
>>> a = 257
>>> b = 257
>>> a is b
False # Новый объект
Итог: Числа от -5 до 256 в CPython работают по принципу паттерна Flyweight (Приспособленец): вместо создания тысяч одинаковых объектов мы используем ссылки на один общий. Хотя в обиходе их часто называют «синглтонами», технически это кешированный пул объектов. В памяти программы может существовать только один экземпляр числа 42. Сколько бы переменных вы ни создали со значением 42, все они будут указывать на один и тот же адрес.
Еще немного магии: Строки (String Interning)
Если с числами всё жестко регламентировано (диапазон от -5 до 256), то со строками ситуация более гибкая. Здесь работает механизм, называемый String Interning (интернирование строк).
Суть та же: Python пытается хранить в памяти только одну копию уникальной неизменяемой строки. Это экономит память, но главное — ускоряет работу словарей (dict), на которых в Python держится вообще всё (пространства имен, атрибуты классов и т.д.). Сравнить два адреса памяти (is) гораздо быстрее, чем побайтово сверять две строки.
Попробуем повторить трюк в консоли:
>>> a = "habr"
>>> b = "habr"
>>> a is b
True
Работает! Python увидел два одинаковых литерала и решил: «Зачем мне плодить сущности? Пусть ссылаются на одно место».
А теперь попробуем что-то посложнее:
>>> a = "habr!" * 100
>>> b = "habr!" * 100
>>> a is b
False
Магия исчезла. Строки одинаковые (== вернет True), но объекты разные.
Как это работает? (Правила фейс-контроля)
Интернирование строк в Python не тотальное, иначе процесс создания любой строки занимал бы слишком много времени на поиск дубликатов. Есть негласные правила, кого пускать в кеш автоматически:
Похоже на имя переменной:
Обычно автоматически интернируются короткие строки, состоящие только из ASCII-букв, цифр и нижнего подчеркивания. Это сделано для оптимизации доступа к атрибутам и именам переменных (они ведь тоже хранятся как строки).
Поэтому"hello_world"скорее всего будет закеширована, а"hello world!"(с пробелом и восклицательным знаком) — уже нет.-
Compile-time vs Run-time:
Строковые литералы, которые Python видит прямо в коде (как"habr"в примере выше), интернируются при компиляции байт-кода.
Но если строка создается динамически в момент выполнения программы, Python обычно ленится её кешировать.Сравните:
>>> a = "hello" + "world" # Конкатенация констант (оптимизируется при компиляции) >>> b = "helloworld" >>> a is b True >>> s1 = "hello" >>> s2 = "world" >>> a = s1 + s2 # Сложение переменных (выполняется в Run-time) >>> b = "helloworld" >>> a is b False
Можно ли управлять этим вручную?
Осторожно: Используйте sys.intern() только для строк, которые часто повторяются и их конечное количество (например, названия тегов в XML, ключи в JSON). Если вы будете интернировать уникальные строки (например, имена пользователей), вы засорите память мусором, который Python не сможет очистить до перезапуска программы.
Да. Если вы пишете приложение, которое обрабатывает миллионы повторяющихся текстовых тегов (например, парсите XML или CSV), вы можете принудительно заставить Python интернировать строку через модуль sys.
import sys
s1 = sys.intern("строка с пробелами и чем угодно")
s2 = sys.intern("строка с пробелами и чем угодно")
print(s1 is s2) # True
Используя sys.intern(), вы гарантируете, что получите ссылку на уже существующую строку, если она есть в таблице интернирования. Это мощный инструмент оптимизации памяти, но пользоваться им стоит осознанно.
Ловушка оптимизатора кода (Code Blocks)
Сейчас кто-то из читателей наверняка скопировал код a = 257; b = 257 в файл main.py, запустил его и готов писать гневный комментарий: «Автор, ты всё врешь! У меня вывело True!».
И этот читатель будет прав. Но и я вас не обманывал. Добро пожаловать в удивительный мир областей видимости и компиляции блоков кода.
REPL vs. Файл
То, о чем мы говорили выше (кеширование чисел до 256), работает глобально на уровне всего интерпретатора. Но есть еще один уровень оптимизации — уровень компилятора.
Интерактивная консоль (REPL): Работает в режиме «прочитал строку — выполнил — забыл».
Когда вы вводитеa = 257и нажимаете Enter, Python компилирует и выполняет эту команду как отдельный блок.
Когда следом вы вводитеb = 257, это уже новый блок. Компилятор не помнит, что было строкой выше, и честно создает новый объект.Скрипт (.py) или функция:
Когда Python запускает файл, он воспринимает его (или тело функции) как единый блок кода.
Компилятор парсит весь блок целиком, находит в нем константы и складывает их в специальную таблицу (co_consts).
Он видит: «Ага, в этом коде дважды встречается число 257. Зачем мне создавать два объекта? Я создам один и буду ссылаться на него в обоих случаях».
Трюк с точкой с запятой
Эту оптимизацию можно увидеть даже в консоли, если записать команды в одну строку:
>>> a = 257; b = 257; a is b
True
Почему? Потому что теперь это одна строка, а значит — один блок компиляции. Python успел сообразить, что может переиспользовать объект 257 для обеих переменных.
Важное различие
Не путайте эти два механизма:
Small Integer Caching (-5..256): Это Run-time оптимизация. Она работает всегда и везде, гарантируя, что
id(256)будет одинаковым хоть в консоли, хоть в скрипте, хоть на Марсе.Оптимизация констант: Это Compile-time оптимизация. Она работает в пределах одного блока кода (функции, модуля). Если вы создадите
a = 257в одной функции, аb = 257в другой — это будут разные объекты (потому что разные блоки кода), иisвернетFalse.
Именно поэтому полагаться на такое поведение нельзя. В разных реализациях Python (Pypy, IronPython) или даже разных версиях CPython оптимизатор может вести себя иначе. Единственная константа в этом хаосе — гарантированный кеш от -5 до 256.
Заключение
Понимание этих механизмов отличает инженера от простого пользователя языка. Знание того, как Python работает с памятью, помогает не только проходить каверзные собеседования, но и отлаживать странные баги, когда логика кажется верной, а результат — нет.
Однако, главный урок сегодняшнего разбора — не в том, как сэкономить 20 байт оперативной памяти. Главный урок — в гигиене кода.
Оптимизации CPython — это детали реализации, которые могут измениться в следующей версии (или отсутствовать в альтернативных реализациях, вроде PyPy). Опираться на них в бизнес-логике — плохая идея.
Золотое правило Python:
Используйте
isтолько для сравнения с синглтонами (None,True,False) или когда вам действительно важно проверить идентичность объектов.Для сравнения чисел, строк и других значений всегда используйте
==.
Пусть магия Python работает на вас, а не против вас.
Мини-квиз: 5 задач на закрепление (проверь себя)
Попробуйте предсказать результат выполнения кода в стандартной консоли CPython (REPL), не запуская его. Ответы спрятаны в описании.
Задача 1: Граница дозволенного
a = -6
b = -6
print(a is b)
Ответ: False.
Почему: Кеширование малых целых чисел работает в диапазоне от -5 до 256. Число -6 в этот диапазон уже не попадает, поэтому создаются два разных объекта.
Задача 2: Арифметика и кеш
a = 250 + 6
b = 256
print(a is b)
Ответ: True.
Почему: Результат вычисления 256 попадает в диапазон кеширования. Python не создает новый объект для результата сложения, а возвращает ссылку на уже существующий в кеше "синглтон" числа 256.
Задача 3: Строковая магия
a = "hello_world"
b = "hello_world"
print(a is b)
Ответ: True (скорее всего).
Почему: Строковые литералы, состоящие только из букв, цифр и подчеркиваний, обычно интернируются автоматически при компиляции. Это не гарантировано спецификацией языка на 100%, но в CPython работает именно так.
Задача 4: Динамические строки
s1 = "py"
s2 = "thon"
a = s1 + s2
b = "python"
print(a is b)
Ответ: False.
Почему: Переменная a вычисляется в Run-time (во время выполнения). Python по умолчанию не интернирует результаты динамической конкатенации строк, поэтому a будет ссылаться на новый объект, отличный от литерала b.
Задача 5: Тот самый One-liner
a = 1000; b = 1000; print(a is b)
Ответ: True.
Почему: Несмотря на то, что 1000 больше 256, код выполняется в одну строку. Для компилятора это один блок кода. Он видит две одинаковые константы и оптимизирует их, создавая один объект для обоих переменных. Если бы команды были введены на разных строках в REPL, результат был бы False.
Анонсы новых статей, полезные материалы, а так же если в процессе у вас возникнут сложности, обсудить их или задать вопрос по этой статье можно в моём Telegram-сообществе. Смело заходите, если что-то пойдет не так, — постараемся разобраться вместе.
Уверен, у вас все получится. Вперед, к экспериментам
Комментарии (18)

MS70
29.11.2025 09:31На 3.12 первый пример не работает (точнее, работает, но не так, как у автора, ), дальше не читал.
a = 257
b = 257print(a is b) # True
python3 --version
Python 3.12.3
enamored_poc Автор
29.11.2025 09:31А вы запускали через cmd? или в файлике?

MS70
29.11.2025 09:31код написал в файлике, запускал в cmd
python3 filename.py
enamored_poc Автор
29.11.2025 09:31Ну так в статье объясняется почему в файлике по другому работает) Прочитай те статью пожалуйста полностью)

AnonimYYYs
29.11.2025 09:31Там может быть от увеличенных размеров кэша до нехитрых оптимизаций (что именно не знаю, не лез внутрь)
Но можете попробовать с: числами больше, как в примере про конкатенацию: 257 is (256 + 1). Возможно на мелких сраюотает, а на больших уже нет: 20**20 is 20**20
Соответственно в вакууме вы вряд ли будете прямо обьявлятт два числа а потом сравнивать. А вот полученное в результате нескольких мат.операций и явно за пределами тфсяч и даже миллионов число сравнивать с другим аналогичным - уже точно вас пошлет куда подальше
А вообще, имхо, это тема довольно глубже, и начинать это изучать надо с плюсовых указателей (или любых других аналогичных). И потом просто кидать ребенка в море, говоря, что любая "переменная" в питоне это просто указатель на обьект. Тобишь, явных указателей тут нет, но неявно они буквально везде. И из этого и приколы с кэшированием малых числэел, и приколы с приравниванием словарей, и еще туда можно поверх много разного допихнуть

python_aggressor
29.11.2025 09:31Статья вводит в заблуждение.
Глянь, про constant folding, собственно, это и будет ответ на твой вопрос.
Когда print (a is b) дает True в указанном случае - это предсказуемое поведение в случае с CPython.И да, статья с разбором этой темы должна быть не про Python как таковой, а про конкретный интерпретатор CPython (Например в IPython, который крутится в jupiter notebook это может работать абсолютно иначе).
Но по сути, это бесполезная статья, которая тебе не пригодится в работе - я не могу представить ситуацию, в которой ты будешь сравнивать две константы int, объявленные тобой же, оператором is.
P.S. Ровно такое объяснение (как в статье) на вопрос про оператор is с int'ами дает, как правило, любая LLM (накину на вентилятор).

Masnin
29.11.2025 09:31Механизм для генерации вопросов на собеседовании) Вроде и понимать все должны, но с другой стороны понимание пригождается раз в пятилетку.

Andrey_Solomatin
29.11.2025 09:31Не должны, это не Питон. Это спецификация одного из интерпретаторов, зависит от версии и флагов компиляции.
Viacheslav-hub
У меня есть подозрения, что эта статья сгенерирована)
Возможно, я не прав, но если это ИИ, то я не понимаю смысла в этих статьях) Какой смысл в наборе количества статей и привлечении в канал, если автор ничего не представляет из себя как специалист
P.S: 48 статей за месяц это конечно сильно)
enamored_poc Автор
Действительно какой смысл в обучающих статья? Сам задаюсь этим вопросом, зачем создавать статьи, в которых объясняется какая то важная тема... Я обязательно подумаю)
Viacheslav-hub
Ничего не имею против обучающего контента) Посмотрел пару других статей и лучше бы некоторых из них не было, так как содержаться не актуальная информация)
Судя по комментариям, это замечаю не только я и стали реально сгенерированы)
Не имею ничего против, но как будто можно перестать публиковать ИИ. При этом не важно обучающий он или нет
myhicursed
когда я это упомянул еще много статей назад - мой комментарий почему-то отклонила модерация)
из минусов такого подхода - там где автор не осведомлен об актуальности информации, он может не понимать некоторых заблуждений, которые выдает ИИ. и соответственно новичок, который только учит язык, запомнит эту инфу, и у него уже фундамент заложится неверный, и с каждым разом будет закрепляться вообще не в то русло.
просто автор так разносторонне выдает статьи/курсы, будто он 20 лет кодил, вместо 3-х, и все либы, фреймворки, ИБ даже знает))
я даже за 4 года могу что-то упускать во время работы на одном постоянном стеке в бэкенде, а здесь - что не спроси, автор все досконально знает)
в общем, не хорошее впечатление складывается
Viacheslav-hub
Для меня, как разработчика в компании, самая большая проблема - новички, которые смотрят на примеры плохой реализации и используют это в своем коде.
Я понимаю, что все "крутятся" как могут, но я ХОТЯ БЫ помечал такие статьи, что они сгенерированы ИИ и ВОЗМОЖНО ( не факт) верифицированы автором ( это зависит от уровня компетенций)
К сожалению, в последнее время таких материалов становится все больше. И собственно я обратил на них внимание, так как они стали попвдать в ленту. Люди, которые вряд ли когда либо работали и будут работать в промышленном IT, пытаются занять свой кусок.
Опять же, это все с моего дивана и я не имею ничего против, но считаю, что читатель должен быть уведомлен о том, что статья сгенерирована или переведена ИИ
victor_shev89
Это очень здравый аргумент.
Мне видится так, что до внедрения ИИ в нашу жизнь, было такое, что человек писал что-то, глубоко заблуждаясь во внутренних механизмах реализации или откровенно их перевирая на свой лад.
Всегда считал, что для этого, в том числе, такие сообщества как хабр и существуют, чтобы верефицировать информацию, дополнять мнения и знания автора или исправлять.
Более того, всегда думал, что и возможность внести изменения в статью, в немалой степени, продиктовано частой необходимостью исправлять такие заблуждения.
victor_shev89
Долгое время думал так же. Но, в итоге пришел к другому мнению:
Использование ИИ ничего не говорит о качестве и уровне специалиста. А вот содержимое сгенерируемого контента - это уже совсем другое. И по уровню контента можно понять насколько человек эксперт.
Использование ИИ - не умственная или профессиональная слабость человека, а попытка оптимизировать усилия. Если вы сильный программист - вы должны отказаться от генерации? Тенденция сейчас другая - ты должен использовать модели, но контролировать их результат через призму своей экспертности.
Сгенерированный контент ты можешь получить через модель любой, а вот тему на которую генерировать и в какую сторону вообще копать, чтобы развиваться - это основная польза. Аналогия: в чем смысл курсов, если вся информация есть в открытом доступе? В структуризации и инкрементальной подаче.
Любой сгенерированный контент так или иначе проходит фильтры генерирующего: он ее читает, правит, дополняет, просит догенерировать какие то важные аспекты с его точки зрения. Не качественный контент - проблема не в генерации, а в человеке, который ее генерирует.
В итоге - не нравится контент, скорее всего вам не понравится и оригинальнвй текст автора. Просто не читайте его. А если интересно и полезно - то какая разница, ИИ это или нет?
enamored_poc Автор
Спасибо за ваше развернутое мнение. С вами я полностью согласен.
Andrey_Solomatin
Вы не учитываете фактор скорости.