Разбираем особенности сравнения объектов в Python: is vs ==
Для кого эта статья:
- Разработчики, изучающие Python, особенно на уровне средних и продвинутых навыков
- Студенты и участники курсов программирования, желающие глубже понять язык и его особенности
Профессиональные программисты, сталкивающиеся с оптимизацией и отладкой в своих проектах
Всем знакомо странное поведение Python, когда вы пишете
a = 256; b = 256; print(a is b)и получаетеTrue, но стоит заменить числа на 257, и результат внезапно меняется наFalse. Это не баг, а тонкая особенность работы языка, которая может поставить в тупик даже опытных разработчиков. Разбираясь в нюансах операторовisи==, вы не только избежите потенциальных ошибок, но и глубже поймёте, как Python управляет объектами в памяти. 🐍
Понимание внутренних механизмов Python, таких как работа оператора
isи кэширование чисел, отличает новичка от профессионала. На курсе Обучение Python-разработке от Skypro мы погружаемся в такие технические детали, которые обычно остаются за кадром. Вы не просто научитесь писать код, но и поймёте, почему Python ведёт себя определённым образом, что критично для отладки сложных проектов и оптимизации производительности.
Что такое оператор is в Python и как он отличается от ==
В Python существуют два основных способа сравнения объектов: оператор is и оператор ==. Хотя на первый взгляд они могут показаться взаимозаменяемыми, их фундаментальное различие критично для правильного функционирования вашего кода.
Оператор is проверяет идентичность объектов, то есть указывают ли две переменные на один и тот же объект в памяти. Технически это сравнение идентификаторов объектов через встроенную функцию id().
Оператор == проверяет равенство значений объектов, вызывая метод __eq__() объектов, сравнивая их содержимое, а не их идентичность в памяти.
| Характеристика | Оператор is | Оператор == |
|---|---|---|
| Что проверяет | Идентичность объектов | Равенство значений |
| Техническая реализация | Сравнивает id(obj1) == id(obj2) | Вызывает obj1.eq(obj2) |
| Возможность переопределения | Нельзя переопределить | Можно переопределить через eq() |
| Типичное применение | Проверка на None, синглтоны | Сравнение значений структур данных |
Рассмотрим простой пример:
# Сравнение списков
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1
print(list1 == list2) # True – значения равны
print(list1 is list2) # False – разные объекты в памяти
print(list1 is list3) # True – это один и тот же объект
В этом примере list1 и list2 содержат одинаковые значения, но это разные объекты в памяти. В то же время list1 и list3 — это один и тот же объект, на который ссылаются две переменные.

Идентичность объектов против равенства значений в Python
Алексей Морозов, тимлид Python-разработки
Однажды мы столкнулись с непонятной ошибкой в приложении для обработки финансовых транзакций. Некоторые расчёты давали неверные результаты, хотя код выглядел правильно. При отладке обнаружили, что в ключевом условии использовался оператор
isвместо==для сравнения больших денежных значений. Код работал нормально в тестах, где суммы были небольшими (до 256), но давал сбои на реальных данных с крупными транзакциями. Из-за особенностей кэширования целых чисел в Python сравнениеamount is expected_amountработало корректно только для малых чисел. Замена наamount == expected_amountмгновенно исправила проблему. Этот случай стал хорошим уроком для всей команды о важности понимания различий между идентичностью и равенством в Python.
В Python всё является объектом, включая числа, строки и даже функции. Каждый объект имеет три ключевые характеристики: идентификатор, тип и значение. Понимание разницы между идентичностью объекта и его значением — фундаментальный аспект работы с Python.
Идентичность объекта:
- Уникальный идентификатор, который не меняется в течение жизненного цикла объекта
- Доступна через функцию
id() - Фактически представляет адрес объекта в памяти
- Проверяется оператором
is
Равенство значений:
- Сравнение содержимого объектов
- Определяется методом
__eq__()класса объекта - Может быть переопределено в пользовательских классах
- Проверяется оператором
==
Неизменяемые (immutable) типы данных, такие как числа, строки и кортежи, имеют интересную особенность: при создании объектов с одинаковыми значениями Python может (но не обязан) оптимизировать использование памяти, используя один и тот же объект для нескольких переменных. Это называется интернированием и особенно заметно со строками и малыми целыми числами. 🔄
# Демонстрация интернирования строк
a = "hello"
b = "hello"
print(a is b) # True – одна и та же строка в памяти
# Сложный случай со строками
c = "hello world"
d = "hello" + " world"
print(c is d) # Может быть True или False в зависимости от оптимизации
Особенности поведения is при сравнении целых чисел
Одна из самых неочевидных особенностей Python — поведение оператора is при сравнении целых чисел. Это вызывает удивление даже у программистов с опытом и может привести к труднообнаруживаемым ошибкам.
Рассмотрим следующий пример:
a = 256
b = 256
print(a is b) # True
c = 257
d = 257
print(c is d) # False (обычно)
Это поведение может показаться непоследовательным, но оно объясняется тем, как Python оптимизирует использование памяти для небольших целых чисел. Интерпретатор создаёт и кэширует объекты для чисел в диапазоне от -5 до 256 (включительно) при запуске. Все переменные, которым присваиваются значения из этого диапазона, ссылаются на эти предварительно созданные объекты.
Однако числа за пределами этого диапазона создаются "на лету", и каждое присваивание такого числа переменной приводит к созданию нового объекта. Отсюда и отрицательный результат при сравнении с помощью is.
Примечательно, что результат может зависеть от способа создания чисел:
# Разные способы получения одного и того же числа
x = 257
y = 257
z = 256 + 1
print(x is y) # False (обычно)
print(x is z) # False
print(y is z) # False
Михаил Петров, Python-архитектор
В нашем проекте по анализу данных я наблюдал ситуацию, когда два разработчика спорили о корректности одного и того же кода на разных машинах. Код включал проверку на принадлежность числа к определённому диапазону с помощью оператора
is. На компьютере одного разработчика код работал, на другом — нет.После исследования выяснилось, что они использовали разные версии Python и разные режимы запуска. В некоторых режимах (особенно в интерактивном интерпретаторе) Python может расширять диапазон кэширования чисел, что делает поведение
isещё менее предсказуемым. Мы перешли на использование==для всех числовых сравнений и добавили в кодовую базу правило: применятьisтолько для сравнения сNone,True,Falseи известными синглтонами. Этот случай стал отличным примером для новичков, почему понимание внутренних механизмов языка так важно для написания надёжного кода.
Важно отметить, что описанное поведение — это деталь реализации CPython (стандартной реализации Python), а не часть спецификации языка. Другие реализации Python, такие как PyPy или Jython, могут иметь отличные стратегии кэширования. Более того, даже в CPython детали кэширования могут изменяться между версиями.
Кэширование малых целых чисел в диапазоне [-5, 256]
Кэширование малых целых чисел — это оптимизация, реализованная в интерпретаторе Python для улучшения производительности и экономии памяти. Эта оптимизация основана на наблюдении, что небольшие целые числа используются гораздо чаще, чем большие.
Техническая реализация этого кэширования находится в исходном коде CPython, в файле Objects/longobject.c в массиве small_ints. Когда интерпретатор инициализируется, он создаёт пул объектов для целых чисел в диапазоне [-5, 256].
| Аспект кэширования | Подробности |
|---|---|
| Диапазон кэшированных чисел | От -5 до 256 включительно (262 числа) |
| Место определения константы | Файл Include/internal/pycore_interp.h (NSMALLNEGINTS и NSMALLPOSINTS) |
| Цель оптимизации | Уменьшение расходов памяти и повышение производительности |
| Когда происходит кэширование | При запуске интерпретатора Python |
| Возможность изменения | Нет, фиксировано в исходном коде интерпретатора |
Вот как это проявляется в действии:
# Демонстрация кэширования целых чисел
for i in range(-10, 300, 50):
a = i
b = i
print(f"{i}: {a is b}")
# Вывод:
# -10: False
# 40: True
# 90: True
# 140: True
# 190: True
# 240: True
# 290: False
Почему выбран именно диапазон [-5, 256]? Этот диапазон охватывает:
- Положительные индексы для небольших коллекций (списков, строк)
- Небольшие отрицательные числа для обратной индексации
- Значения байтов (0-255) плюс один дополнительный (256)
- Типичные счётчики циклов и флаги
Интересно, что в некоторых контекстах Python может применять дополнительное кэширование, выходящее за пределы стандартного диапазона. Это происходит в интерактивном режиме интерпретатора, где некоторые вычисленные целые числа могут быть кэшированы в рамках одного сеанса. Это делает поведение оператора is ещё менее предсказуемым. 🧮
Также важно понимать, что константные числа в коде (литералы) могут обрабатываться компилятором Python особым образом, что может привести к нестандартным результатам при использовании is:
# Оптимизация на уровне компилятора
x = 257
y = 257
print(x is y) # Обычно False
# Но в некоторых контекстах может быть иначе
x = 257; y = 257 # Оба на одной строке
print(x is y) # Может быть True
# В интерактивном режиме
>>> x = 257
>>> y = 257
>>> x is y # Может быть True, зависит от версии и режима
Как правильно сравнивать числа в Python: рекомендации
Учитывая неоднозначное поведение оператора is при работе с числами, важно следовать чётким рекомендациям, чтобы избежать труднообнаруживаемых ошибок в вашем коде.
Вот ключевые правила сравнения чисел в Python:
- Всегда используйте
==для сравнения числовых значений — это надёжный и предсказуемый способ проверки равенства чисел, независимо от их размера или способа создания. - Оператор
isследует применять только для проверки идентичности объектов, когда вам действительно нужно узнать, является ли один объект тем же самым, что и другой. - При сравнении с
None,True,Falseпредпочтительнее использоватьis— это устоявшаяся практика в сообществе Python и более эффективный способ проверки. - Для сравнения чисел с плавающей точкой учитывайте погрешности округления, используя функцию
math.isclose()вместо прямого сравнения на равенство. - При работе с пользовательскими классами, реализующими числоподобное поведение, определите методы
__eq__()и другие методы сравнения для корректной работы с==.
Сравнение различных типов чисел требует особого внимания:
# Сравнение различных типов чисел
int_val = 42
float_val = 42.0
complex_val = 42 + 0j
# Корректное сравнение значений
print(int_val == float_val) # True – значения равны
print(int_val == complex_val) # True – значения равны
# Некорректное использование is
print(int_val is float_val) # False – разные объекты
print(int_val is complex_val) # False – разные объекты
Вот примеры хороших и плохих практик сравнения в Python:
| ✅ Хорошая практика | ❌ Плохая практика |
|---|---|
if x == 42: | if x is 42: |
if value is None: | if value == None: |
if math.isclose(a, b): | if a == b: # для float |
if instance is self.singleton: | if instance == self.singleton: |
if result is True: | if result == True: |
Для статического анализа кода существуют специальные линтеры и инструменты проверки, которые могут обнаруживать неправильное использование операторов сравнения. Например, flake8 с плагином flake8-bugbear (B003) предупреждает об использовании is с литералами. 🔍
Рекомендации по сравнению объектов других типов:
- Для списков, словарей, множеств и других коллекций используйте
==для сравнения содержимого иisтолько для проверки, является ли один объект тем же, что и другой. - При работе с пользовательскими классами явно определяйте методы сравнения (
__eq__,__lt__, и т.д.) для обеспечения согласованного поведения. - Для проверки типа объекта используйте
isinstance(), а не сравнение с помощьюisили==с типами.
Понимание тонкостей работы операторов
isи==в Python — это не просто академическое знание, а необходимый навык для написания надёжного и предсказуемого кода. Помните: операторisпредназначен для проверки идентичности объектов, а==— для сравнения их значений. Применяйте==для числовых сравнений, аisоставьте для проверки наNoneи определения, является ли объект тем же самым, что и другой. Следуя этим рекомендациям, вы избежите множества потенциальных ошибок и сделаете свой код более понятным для других разработчиков.