UpPetto

This is a real course generated by UpPetto — unedited.

AI-generated. Two models wrote it, a third checked every risky claim against sources, a fourth re-checked.

Create my own course

is vs ==, интернирование и мелкие числа: где интуиция C.pointer ломается

core правильно выбрать is или == в конкретной ситуации и объяснить, почему 'a is b' может внезапно быть True или False

В C сравнение указателей (p == q) и сравнение содержимого (*p == *q, strcmp, memcmp) — два явно разных выражения, и вы их не путаете, потому что синтаксис разный. В Python эта разница есть, но выражена выбором оператора:

  • a == b — равенство (equality): одинаковые ли значения. За это отвечает метод __eq__ объекта. Аналог strcmp/memcmp.
  • a is b — идентичность (identity): являются ли a и b именами одного и того же объекта. Эквивалент id(a) == id(b). Прямой аналог сравнения указателей.

Правило простое и должно быть автоматическим: используйте ==, когда хотите узнать, равны ли значения; используйте is, только когда хотите узнать, является ли объект тем самым объектом — то есть практически всегда для сравнения с синглтоном None (а также True, False) и иногда для sentinel-объектов вашего собственного дизайна.

`is` в Python — аналог сравнения адресов в C, а `==` — сравнения содержимого
`is` в Python — аналог сравнения адресов в C, а `==` — сравнения содержимого

Осложняют дело оптимизации CPython: кэширование мелких целых чисел (обычно примерно от -5 до 256) и интернирование некоторых строк, похожих на идентификаторы. Одинаковые «мелкие» неизменяемые объекты не создаются заново — переиспользуется один объект. Из-за этого a = 256; b = 256; a is b часто оказывается True — не потому что язык это гарантирует, а потому что оба имени случайно ссылаются на один закэшированный объект. Стоит выйти за диапазон кэша (x = 257; y = 257) — is скорее всего станет False, хотя == по-прежнему True.

Это деталь реализации CPython, а не гарантия языка: диапазон может меняться между версиями и в других реализациях (PyPy, Jython). Код if val is 256: — бомба замедленного действия; правильный код — if val == 256:.

Разобранный пример

a = 256
b = 256
print(a is b)     # обычно True — объект из кэша мелких int в CPython

c = 1000
d = 1000
print(c is d)     # обычно False — за пределами кэша, два разных объекта
print(c == d)     # True — сравниваются значения

e = 1000
f = e
print(e is f)     # True — f привязан к тому же объекту, что и e

Рассуждение: первая пара совпадает по is не потому что «Python умный», а потому что реализация переиспользует предсозданные объекты для небольших чисел. Вторая пара — два отдельных литерала, каждый может создать свой объект; is здесь непредсказуем по спецификации, а == надёжен. Третья пара — не совпадение реализации, а прямой aliasing: здесь is гарантированно True.

То же с строками: литерал в коде может быть интернирован, а строка, собранная в рантайме, — нет:

status = "".join(["N", "E", "W"])
print(status == "NEW")   # True — надёжно
print(status is "NEW")   # False — другой объект

Поэтому проверка if task["status"] is "NEW": «работает» на литералах в тестах и молча ломается на данных из БД или API.

Отдельный частый случай — проверка на None:

if x is None:      # правильно: None — единственный синглтон
    ...
if x == None:      # работает, но зависит от __eq__ объекта x
    ...

is None предпочтителен не по стилю: он не зависит от того, как объект x определяет __eq__ — некоторые библиотечные объекты переопределяют == так, что x == None не возвращает обычное булево значение.

Попробуй сейчас

Не запуская код, ответьте и объясните почему:

  1. [1, 2] is [1, 2] — True или False?
  2. x = None; y = None; x is y — True или False?
  3. s1 = "hello world!"; s2 = "hello world!"; s1 is s2 — надёжно True, надёжно False, или зависит от реализации?

Ожидаемые ответы: (1) False — каждый литерал списка создаёт новый объект; изменяемые объекты не кэшируются. (2) True — None всегда один и тот же объект-синглтон. (3) Зависит от реализации — строки с пробелом обычно не интернируются автоматически (в отличие от строк, похожих на идентификаторы), гарантий нет; для сравнения значений всегда ==.

Получилось, если…

Вы разобрались, если на любое выражение с is можете сразу сказать: «это про идентичность конкретного объекта (синглтон/aliasing) или это случайность кэширования CPython, на которую нельзя опираться» — и для сравнения значений автоматически берёте ==, оставляя is для None, True, False и явных sentinel-объектов.

Вывод

is — идентичность (id), == — значение через __eq__; в C для этого были разные выражения, в Python — разные операторы, и их легко перепутать по инерции. Кэширование мелких int и интернирование части строк — оптимизации реализации, а не гарантии языка: код, который «работает» благодаря is для чисел или строк, — скрытый баг.

AI-generated · source-grounded review

🛡 Fact-checked: 2 risky claims verified · 4 removed · confidence: high · figures: 2 · a second model checked these claims and agreed
[verified] `is` проверяет идентичность (id), `==` — равенство через `__eq__`
База знаний: «Идентичность объекта проверяется оператором is, а равенство — ==»
[softened] CPython кэширует мелкие целые примерно от -5 до 256, поэтому `a is b` для 256 часто True, а для 257 False
Диапазон отсутствует в базе знаний; оставлено с оговорками «обычно/примерно» и явной пометкой «деталь реализации CPython»
[softened] Интернирование строк — деталь реализации; строка, собранная в рантайме, обычно не идентична литералу
База знаний не описывает интернирование; формулировки ослаблены («может быть интернирован», «обычно»)
[softened] `x == None` может быть переопределено numpy так, что не вернёт булево значение
Конкретика про numpy отсутствует в базе знаний — заменено на «некоторые библиотечные объекты»
[verified] SVG: C `p == q` — сравнение адресов; Python `a is b` — сравнение id, два имени на один объект дают True
Соответствует описанию identity vs equality в базе знаний
[softened] SVG: a и b → один кэшированный int 256; x и y → два разных объекта int 257
Диаграмма сохранена, метка кэша переформулирована как деталь реализации CPython, поскольку диапазон не подтверждён базой знаний
[removed] Диапазон кэширования мелких int оставлен со смягчением («обычно/примерно -5..256», «деталь реализации CPython») — точный диапазон отсутствует в базе знаний; в метке SVG формулировка также смягчена
[removed] Утверждение версии A «id(a) == id(b) — сравнение адресов» ослаблено до «совпадение id()», адрес упомянут только как аналогия в C
[removed] Из примера версии B про статусы убрано утверждение о том, что интерпретатор «склеивает» одинаковые литералы как гарантированное поведение; оставлено «может быть интернирован»
[removed] Приписывание numpy конкретного поведения `x == None` заменено на нейтральное «некоторые библиотечные объекты» — конкретика отсутствует в базе знаний
Key concepts: identity vs equality interning small int caching
Tell me more 🔒 Didn't understand — explain simply 🔒 Show examples 🔒 Sources 🔒

On your own course these buttons answer instantly, quizzes track what you've mastered, and lessons adapt to your gaps. Write my course

Check yourself

1. Что проверяет оператор `is` в Python?
2. Почему `a = 500; b = 500; print(a is b)` скорее всего выведет False, а `a = 100; b = 100` часто True?
3. Почему `x is None` предпочтительнее, чем `x == None`?
4. Коллега написал `if my_list is []:`. Почему это почти всегда ошибка?
On your own course, these are marked as you answer