is против ==: одно спрашивает про тождество, второе вызывает метод — и половина известных сюрпризов вообще не про них
«is сравнивает объекты, == сравнивает значения» — верно и бесполезно: на вопрос «почему у меня 1000 is 1000 даёт True» это не отвечает. Ответов там три разных: кеш малых чисел, объединение констант компилятором и интернирование строк. Опираться в коде нельзя ни на один из трёх.
Полное техническое изложение
TL;DR
Два оператора отвечают на два разных вопроса. is спрашивает: это тот же
самый объект? == спрашивает: считаются ли эти значения равными? Первое —
контракт языка: true if and only if x and y are the same object
(истинно тогда и только тогда, когда x и y — один и тот же объект), и переопределить это нечем — метода __is__ нет. Второе — вызов
__eq__, то есть обычного метода, который может вернуть что угодно и
стоить сколько угодно. Поэтому is уместен там, где контракт самого API — про
тождество: у объектов, существующих в единственном экземпляре, и у
собственных меток, заведённых ровно ради сравнения по тождеству.
Отсюда главное следствие: is бывает истинным там, где вы этого не писали, —
и не по одной причине, а по трём разным. Кеш малых чисел — от −5 до 256
включительно. Объединение равных констант компилятором — из-за него
1000 is 1000 в одном файле даёт True, а в разных файлах False. Интернирование
строк — литерал, похожий на идентификатор, оказывается одним объектом на всю
программу; на строку, собранную во время выполнения, полагаться нельзя.
Различать эти три важнее, чем знать про каждый: ни один из них не обещан
языком.
Дальше — то, что отличает знающего от читавшего. Границы кеша одинаковы в
проверенных сборках 3.11–3.14, и компилятор с 3.8 предупреждает об is с
литералом сам. Сравнением указателей тождество реализовано в CPython, и в
другой реализации оно может быть устроено иначе. Цена: is — 11,8–12,1 нс
независимо ни от чего; == на равных строках по двести тысяч символов —
4701 нс, в четыреста раз дороже (3.13.7). Но на коротких строках разница всего
вдвое, и совет «пишите is» отсюда не следует.
- имя в программе связано с объектом, и одному объекту может соответствовать несколько имён;
- два разных объекта могут хранить одно и то же содержимое;
- класс может задавать своё поведение операторов методами с двойным подчёркиванием.
- кеш малых чисел, объединение констант компилятором, интернирование строк;
__eq__,id(),sys.intern,nan, вечные объекты.
База: один и тот же объект — или одинаковое содержимое
Прежде чем говорить о кешах и интернировании, стоит назвать обычными словами, что спрашивает каждый из двух операторов. Вопроса ровно два, и они разные:
a is bспрашивает — это тот же самый объект?a == bспрашивает — считаются ли эти значения равными?
Разницу видно на трёх переменных, где два имени указывают на один объект, а третье — на отдельный объект с тем же содержимым:
first = [1, 2, 3]
second = first # второе имя для того же самого объекта
third = [1, 2, 3] # отдельный объект, содержимое такое же
print(first is second) # True — объект один
print(first is third) # False — объекта два
print(first == third) # True — содержимое одинаковоеТретья строка и есть вся суть: first и third равны и при этом не
тождественны. Проверить это можно и с другой стороны — дописать элемент в
first: second изменится вместе с ним, потому что это один объект, а third
останется прежним.
И вот главный вопрос урока: почему тогда два одинаковых литерала иногда
оказываются одним объектом? Написали 1000 дважды — а is отвечает True,
хотя по смыслу это должно быть два разных объекта, как first и third.
Этого уже достаточно, чтобы ответить на базовый вопрос собеседования: is —
про тождество, == — про равенство значений, и подменять один другим нельзя.
Всё дальнейшее — про то, откуда берётся это лишнее True, почему причин у него
три разных и почему ни на одну из них нельзя опираться в коде.
Механизм 1: что делает каждый из двух
is истинно тогда и только тогда, когда это один и тот же объект. Сравнение указателей — уже реализация.Определение is в справочнике занимает одно предложение, и в нём нет ни слова
про значения.
The operators is and is not test for an object's identity: x is y is true
if and only if x and y are the same object. An Object's identity is
determined using the id() function.
Операторы is и is not проверяют тождество объекта: x is y истинно тогда и только тогда, когда x и y — один и тот же объект. Тождество объекта определяется функцией id().
А что такое тождество — сказано в модели данных; там оно названо
идентичностью, слово то же самое: An object's identity never changes once it has been created; you may think of it as the object's address in memory
(Идентичность объекта никогда не меняется после его создания; можно считать её адресом объекта в памяти). Для CPython это не «можно считать», а буквально так и есть — там же стоит пометка о реализации.
Границу между этими двумя утверждениями стоит держать в голове весь урок.
Контракт языка — «тот же объект или нет»; то, что тождество реализовано как
адрес в памяти, — свойство CPython, а не обещание языка. Ответ «is сравнивает
адреса» верен только как ответ на вопрос «как это сделано»; на вопрос «что это
значит» он не отвечает.
Из этого следуют три вещи сразу:
isнельзя переопределить. Нет метода__is__. В CPython оператор компилируется в инструкциюIS_OP, которая сравнивает два указателя, — но это деталь реализации, а не определение оператора.isне зависит от типа и размера. Сравнить два стомегабайтных объекта ровно так же дёшево, как два числа.==— противоположность в каждом: его переопределяют, и он зависит и от типа, и от размера. Это вызов__eq__, который может вернуть не-булево значение, обойти миллион элементов или уйти в сеть.
Механизм 2: где is — правильный инструмент
Правило «пишите is только с None» короткое, но неверное: оно не про None,
а про тождество как контракт. Есть объекты, у которых API обещает
единственность, и спрашивать про них нужно именно is — сравнение по значению
здесь либо лишнее, либо опасное. Прогон bench/identity/contract.py, блок 3:
None is None True
NotImplemented is NotImplemented True
Ellipsis is ... True
Color.RED is Color(1) True
Тот же список продолжается собственным часовым — и это тот случай, где
is не заменить ничем:
MISSING = object()
def find(mapping, key):
got = mapping.get(key, MISSING)
return "ключа нет" if got is MISSING else got
find({"k": None}, "k") # None — ключ есть, значение None
find({}, "k") # 'ключа нет'== MISSING тут не годится по той же причине, по которой не годится
== None: равенство спрашивает у чужого объекта, а часовой — про тождество.
Важно, где проходит граница правила. Она не по списку из четырёх строк выше:
None, NotImplemented, Ellipsis и члены Enum — это примеры, а не
полный перечень. Правило общее: is уместен у любого объекта, который
существует в единственном экземпляре, и у любой метки, заведённой специально
ради сравнения по тождеству. Собственный MISSING = object() в этот список
входит ровно на тех же правах, что и None, — и точно так же туда войдёт любой
другой часовой, который вы заведёте завтра.
А вот is True и is False в этот список НЕ входят, хотя тоже одиночки:
проверка x is True отклонит 1, непустую строку и всё прочее истинное. Это
не вопрос о тождестве, это вопрос об истинности, заданный не тем оператором.
Механизм 3: где is даёт True не потому, что вы так написали
Вот тут и живёт настоящая путаница. Ответ «is сравнивает
объекты» не объясняет, почему 1000 is 1000 даёт True — а объясняют это три
РАЗНЫХ механизма, и они разной прочности.
Под каждой строкой картинки стоит пометка, на чём держится ответ: на обещании справочника, на устройстве CPython или на том, попали ли литералы в одну компиляцию. В код переносится только первое.
Разберём каждый.
Кеш малых чисел. Числа от −5 до 256 создаются при старте интерпретатора и раздаются готовыми. Проверять это нужно осторожно — числами, которые компилятор не может посчитать заранее:
print(int("256") is int("256")) # True
print(int("257") is int("257")) # FalseГраницы одинаковы на 3.11, 3.12, 3.13 и 3.14.7. Но это деталь CPython, а не обещание языка.
Объединение констант. А вот это уже не про числа вовсе:
def first(): return 1000
def second(): return 1000
print(first() is second()) # TrueКомпилятор объединяет равные константы в пределах одной компиляции — даже когда они лежат в разных функциях. Перенесите одну функцию в другой файл, и тот же код даст False. То есть первое True — свойство того, как СОБРАН исходник, а не свойство чисел.
Интернирование строк. Правило простое: литерал, похожий на идентификатор,
интернируется — один объект на всю программу, включая другие файлы. На
автоматическое интернирование строки, собранной во время выполнения, полагаться
нельзя — даже если по виду она идентификатор. Обратное тоже верно: is иногда
даёт True и без интернирования, когда операция вернула тот же объект.
Проверено: "".join([s]) с одним элементом, полный срез s[:] и str(s)
возвращают исходную строку, а пустая строка и односимвольные latin-1 — вообще
кешированные синглтоны.
import sys
ident = "hello_world"
built = "".join(["hello", "_", "world"])
print(built is ident) # False
print(sys.intern(built) is ident) # TrueДокументация говорит и зачем это сделано: the key comparisons (after hashing) can be done by a pointer compare instead of a string compare
(сравнение ключей (после хеширования) может выполняться сравнением указателей вместо сравнения строк). Интернирование существует ради скорости словарей, а не ради того, чтобы вы писали is со строками.
Компилятор, кстати, предупреждает об этом сам, и с 3.8:
SyntaxWarning: "is" with 'str' literal. Did you mean "=="?
Механизм 4: nan — единственное значение, где расходится всё
nan = float("nan")
print(nan == nan) # False
print(nan is nan) # True
print(nan in [nan]) # True
print([nan] == [nan]) # TrueПервая строка — требование стандарта с плавающей точкой. Третью объясняет справочник — он даёт точную эквивалентность для проверки вхождения.
For container types such as list, tuple, set, frozenset, dict, or
collections.deque, the expression x in y is equivalent to
any(x is e or x == e for e in y).
Для контейнерных типов, таких как list, tuple, set, frozenset, dict или collections.deque, выражение x in y эквивалентно any(x is e or x == e for e in y).
Тождество проверяется первым — это записано прямо в порядке операндов
or. Поэтому тот же самый nan в списке находится, а другой nan с тем же
значением — нет:
print(float("nan") in [nan]) # FalseЧетвёртая строка держится на том же порядке: список сравнивает элементы сначала по тождеству и только потом по равенству. Но это уже устройство CPython, а не обещание языка — справочник описывает так проверку вхождения, а не сравнение списков.
То же правило объясняет, почему nan работает ключом словаря: положили и
достали одним и тем же объектом — сработало тождество.
Механизм 5: is None, а не == None
Правило известное, а причина у него ровно одна: == — это вызов метода, и
метод может соврать.
class AlwaysEqual:
def __eq__(self, other):
return True
obj = AlwaysEqual()
print(obj == None) # True
print(obj is None) # FalseПричём __eq__ не обязан возвращать даже булево значение:
class Weird:
def __eq__(self, other):
return "не булево значение"
print(bool(Weird() == 1)) # True — непустая строка истиннаif x == None: в такой ситуации молча уйдёт не в ту ветку. is None
перехватить нечем: он спрашивает про тождество, а метода для этого вопроса нет.
То же касается is True и is False, но с обратным выводом: писать так не
надо. Проверка x is True отклонит 1, непустую строку и всё остальное
истинное — почти всегда это ошибка, а не намерение.
Механизм 6: id уникален не всегда
print(id([]) == id([])) # TrueНикакого чуда: первый список умирает до того, как создаётся второй, и адрес достаётся второму. Документация оговаривает это прямо.
This is an integer which is guaranteed to be unique and constant for this object
during its lifetime. Two objects with non-overlapping lifetimes may have the
same id() value.
Это целое число, гарантированно уникальное и постоянное для данного объекта в течение его жизни. Два объекта с непересекающимися временами жизни могут иметь одно и то же значение id().
Практический вывод: id(a) == id(b) — не замена a is b, а его худшая версия.
Она даёт тот же ответ, только когда оба объекта живы; сохранённый в переменную
id не значит ничего.
Глубже: что это стоит
== вместе с длиной.Картинка говорит не «пишите is» — она показывает, как устроена цена каждого
из двух.
is — постоянная цена: в CPython сравниваются два указателя, и делается это
одинаково, идёт ли речь о
числе или о строке в двести тысяч символов. == растёт вместе с длиной,
потому что равные строки приходится дочитать до конца: различия, на котором
можно остановиться, у них нет. Строки, различающиеся с первого символа,
сравниваются быстрее равных — 19,58 против 24,62 нс. В таблице роста то же
сравнение дало 24,29: числа сравнимы только внутри своего блока, а разница в
четверть процента между блоками — шум машины.
Отдельно стоит объект со своим __eq__ на Python: 101,83 нс против 24,68 нс
у объекта без него, то есть вчетверо. Метод на Python дороже сравнения, которое
интерпретатор делает сам, и это стоит помнить в горячем цикле — но чинится это
не заменой на is, а тем, чтобы не сравнивать в цикле лишнего.
Как отвечать на собеседовании
Короткий ответ: is спрашивает, один ли это объект; его нельзя
переопределить, и его цена не зависит от объекта. == вызывает __eq__,
обычный метод, который может вернуть что угодно. is уместен там, где контракт
API именно про тождество: у любого объекта-одиночки и у любой метки, заведённой
ради такого сравнения, — None, NotImplemented, Ellipsis, члены Enum,
собственный часовой object().
Этого достаточно, чтобы ответить верно. Дальше — то, что добавляют, если собеседник копает.
Если интервьюер копает глубже
Первое, что стоит развести самому, не дожидаясь вопроса: сравнение по
тождеству — контракт языка, а адрес в памяти — деталь CPython. Ответ «is
сравнивает адреса» отвечает на вопрос «как это сделано», а не на вопрос «что
оператор обещает»; на другой реализации тождество может быть устроено иначе, и
контракт от этого не изменится.
Если спросят про 1000 is 1000, правильный ответ — назвать все три механизма
и не перепутать их: кеш малых чисел кончается на 256, объединение констант
работает в пределах одной компиляции, интернирование — про строки, похожие на
идентификаторы. И добавить, что компилятор с 3.8 предупреждает об этом сам,
формулируя ровно так: These can often work by accident in CPython, but are not guaranteed by the language spec
(В CPython они часто работают случайно, но спецификацией языка не гарантированы).
И одна вещь, на которой легко переусердствовать, — цена. Сказать «is в
четыреста раз дешевле» значит выдать один замер за общее правило: четыреста
получились на равных строках по двести тысяч символов и на одной машине, а на
коротких строках разница всего вдвое. Правило пишется не от числа, а от
границы: is стоит одинаково всегда, == растёт вместе с длиной, и выбирают
между ними не по цене, а по тому, какой вопрос задан.
Дальше спросят
Сравнили два одинаковых литерала через is, получили True. Можно на это опираться?
Нет. True здесь получается не потому, что так написано в коде, а потому, что
интерпретатор переиспользовал объект — это деталь реализации, а не гарантия
языка, и на другой сборке или в другом контексте её может не быть.
Есть значение, для которого x == x ложно. Какое и что из этого следует?
nan — единственное, где расходится всё: x == x даёт False, а x is x
по-прежнему True. Отсюда и практическое следствие про контейнеры: поиск в них
устроен так, что nan в списке находится, хотя равным самому себе не является.
id() уникален?
В пределах времени жизни объекта — да. Но после освобождения объекта тот же
id может достаться другому, поэтому хранить id и сравнивать его позже —
способ получить ложное совпадение.
Частые заблуждения
1000 is 1000 даёт True из-за кеша чисел
Кеш кончается на 256 — int("257") is int("257") даёт False. True в исходнике получается по другой причине: компилятор объединяет равные константы в пределах ОДНОЙ компиляции, и работает это даже для литералов в разных функциях одного файла. Тот же литерал из другого файла даёт False. Проверено на 3.11, 3.12, 3.13 и 3.14.7.
Все одинаковые строковые литералы — один объект
Только похожие на идентификатор. "hello_world" интернируется и совпадает даже между файлами, "hello world" с пробелом — нет. А на автоматическое интернирование строки, собранной во время выполнения, полагаться нельзя: "".join(["hello", "_", "world"]) даёт новый объект, пока его не пропустят через sys.intern. Но и обратного правила нет — "".join([s]) с одним элементом вернёт саму s.
nan не равен себе, значит его нельзя найти в списке
Находится. Справочник даёт точную эквивалентность: x in y — это any(x is e or x == e for e in y), и тождество проверяется ПЕРВЫМ. Тот же самый объект nan находится по is, а другой nan с тем же значением — нет. По той же причине nan работает ключом словаря, если класть и доставать одним объектом.
id(a) == id(b) — то же самое, что a is b
Только пока оба объекта живы. Документация оговаривает прямо: два объекта с непересекающимися временами жизни могут иметь одинаковый id. Отсюда id([]) == id([]), дающее True: первый список умирает до создания второго, и адрес достаётся второму.
is быстрее, поэтому там, где можно, лучше писать его
«Там, где можно» — это не список, а правило: любой объект, существующий в единственном экземпляре, и любая метка, заведённая ради сравнения по тождеству. None, NotImplemented, Ellipsis, члены Enum и собственный object() — примеры этого правила. На равных строках по 200 символов разница между is и == всего вдвое (12,01 против 24,29 нс, 3.13.7); четырёхсоткратной она становится на строках по двести тысяч символов, а такие в сравнении встречаются редко. Главное же в другом: на РАЗНЫХ объектах с одинаковым значением is отвечает неверно, и делает это быстро.
История версий
| Версия | Изменение | Что это значит для кода |
|---|---|---|
| 3.8 | Компилятор начинает выдавать SyntaxWarning на is с литералом — с прямой формулировкой причины: в CPython такие проверки часто работают случайно, но спецификацией не гарантированы. Практически это значит, что половину ошибок этого урока теперь ловит сам компилятор, если не глушить предупреждения. | |
| 3.12 | PEP 683: None, True, False и малые числа становятся вечными и перестают считать ссылки вовсе. На is это не влияет никак, но меняет вывод sys.getrefcount: вместо осмысленного числа он возвращает служебную константу. Читать её как «сколько ссылок» больше нельзя. | |
| 3.14 | Значение той самой служебной константы меняется: на 3.12 и 3.13 sys.getrefcount(None) даёт 4294967295, на 3.14.7 — 3221225472. Ни на одну проверку тождества это не влияет. Читать это число как счётчик ссылок нельзя ни в одной версии: оно служебное, и значение у него своё в каждой. |
Практика
Две задачи. Сначала ответьте, потом сверьтесь с настоящим выводом: в обеих правильный ответ взят из прогона скрипта, а не назначен.
Практика · что напечатает
a = int("257")
b = int("257")
x = int("255")
y = int("255")
print(a == b)
print(a is b)
print(x is y)Практика · оцените
Проверьте себя
В одном файле: def first(): return 1000 и def second(): return 1000. Что вернёт first() is second() и почему?
Чем измерено
Числа этой статьи получены этими скриптами. Каждый открывается прямо отсюда — вместе с записью прогона: на чём считали, что получилось и с каким разбросом.
Это не пересказ и не отдельный текст: всё ниже взято из самой статьи — её выжимка, заголовки разборов, колонка «на самом деле» и таблица версий. Поэтому разойтись со статьёй эти тезисы не могут.
Суть
- Два оператора отвечают на два разных вопроса.
isспрашивает: это тот же самый объект?==спрашивает: считаются ли эти значения равными? Первое — контракт языка: true if and only if x and y are the same object, и переопределить это нечем — метода__is__нет. Второе — вызов__eq__, то есть обычного метода, который может вернуть что угодно и стоить сколько угодно. Поэтомуisуместен там, где контракт самого API — про тождество: у объектов, существующих в единственном экземпляре, и у собственных меток, заведённых ровно ради сравнения по тождеству. - Отсюда главное следствие:
isбывает истинным там, где вы этого не писали, — и не по одной причине, а по трём разным. Кеш малых чисел — от −5 до 256 включительно. Объединение равных констант компилятором — из-за него1000 is 1000в одном файле даёт True, а в разных файлах False. Интернирование строк — литерал, похожий на идентификатор, оказывается одним объектом на всю программу; на строку, собранную во время выполнения, полагаться нельзя. Различать эти три важнее, чем знать про каждый: ни один из них не обещан языком. - Дальше — то, что отличает знающего от читавшего. Границы кеша одинаковы в проверенных сборках 3.11–3.14, и компилятор с 3.8 предупреждает об
isс литералом сам. Сравнением указателей тождество реализовано в CPython, и в другой реализации оно может быть устроено иначе. Цена:is— 11,8–12,1 нс независимо ни от чего;==на равных строках по двести тысяч символов — 4701 нс, в четыреста раз дороже (3.13.7). Но на коротких строках разница всего вдвое, и совет «пишитеis» отсюда не следует.
На самом деле
- Кеш кончается на 256 —
int("257") is int("257")даёт False. True в исходнике получается по другой причине: компилятор объединяет равные константы в пределах ОДНОЙ компиляции, и работает это даже для литералов в разных функциях одного файла. Тот же литерал из другого файла даёт False. Проверено на 3.11, 3.12, 3.13 и 3.14.7. - Только похожие на идентификатор.
"hello_world"интернируется и совпадает даже между файлами,"hello world"с пробелом — нет. А на автоматическое интернирование строки, собранной во время выполнения, полагаться нельзя:"".join(["hello", "_", "world"])даёт новый объект, пока его не пропустят черезsys.intern. Но и обратного правила нет —"".join([s])с одним элементом вернёт самуs. - Находится. Справочник даёт точную эквивалентность:
x in y— этоany(x is e or x == e for e in y), и тождество проверяется ПЕРВЫМ. Тот же самый объектnanнаходится поis, а другойnanс тем же значением — нет. По той же причинеnanработает ключом словаря, если класть и доставать одним объектом. - Только пока оба объекта живы. Документация оговаривает прямо: два объекта с непересекающимися временами жизни могут иметь одинаковый
id. Отсюдаid([]) == id([]), дающее True: первый список умирает до создания второго, и адрес достаётся второму. - «Там, где можно» — это не список, а правило: любой объект, существующий в единственном экземпляре, и любая метка, заведённая ради сравнения по тождеству.
None,NotImplemented,Ellipsis, членыEnumи собственныйobject()— примеры этого правила. На равных строках по 200 символов разница междуisи==всего вдвое (12,01 против 24,29 нс, 3.13.7); четырёхсоткратной она становится на строках по двести тысяч символов, а такие в сравнении встречаются редко. Главное же в другом: на РАЗНЫХ объектах с одинаковым значениемisотвечает неверно, и делает это быстро.
По версиям
- 3.8
- Компилятор начинает выдавать
SyntaxWarningнаisс литералом — с прямой формулировкой причины: в CPython такие проверки часто работают случайно, но спецификацией не гарантированы. Практически это значит, что половину ошибок этого урока теперь ловит сам компилятор, если не глушить предупреждения.< - 3.12
- PEP 683:
None,True,Falseи малые числа становятся вечными и перестают считать ссылки вовсе. Наisэто не влияет никак, но меняет выводsys.getrefcount: вместо осмысленного числа он возвращает служебную константу. Читать её как «сколько ссылок» больше нельзя.< - 3.14
- Значение той самой служебной константы меняется: на 3.12 и 3.13
sys.getrefcount(None)даёт 4294967295, на 3.14.7 — 3221225472. Ни на одну проверку тождества это не влияет. Читать это число как счётчик ссылок нельзя ни в одной версии: оно служебное, и значение у него своё в каждой.<
Что разобрано
- База: один и тот же объект — или одинаковое содержимое
- Механизм 1: что делает каждый из двух
- Механизм 2: где `is` — правильный инструмент
- Механизм 3: где `is` даёт True не потому, что вы так написали
- Механизм 4: `nan` — единственное значение, где расходится всё
- Механизм 5: `is None`, а не `== None`
- Механизм 6: `id` уникален не всегда
- Глубже: что это стоит
- Как отвечать на собеседовании
- Дальше спросят
- Частые заблуждения
- История версий
- Практика
- Проверьте себя
- Чем измерено
Источники и что читать дальше
7 ИСТОЧНИКОВ
- Справочник языка — сравнение по тождествуОфициальная документация. Определение оператора дословно: «The operators is and is not test for an object's identity: x is y is true if and only if x and y are the same object. An Object's identity is determined using the id() function» (Операторы is и is not проверяют тождество объекта: x is y истинно тогда и только тогда, когда x и y — один и тот же объект. Тождество объекта определяется функцией id()). Из этого определения следует всё остальное: перехватить оператор нечем, и от типов он не зависит.https://docs.python.org/3.14/reference/expressions.html#is-not
- Модель данных — идентичность объектаОфициальная документация. Откуда взято утверждение, что идентичность не меняется: «Every object has an identity, a type and a value. An object's identity never changes once it has been created; you may think of it as the object's address in memory» (У каждого объекта есть идентичность, тип и значение. Идентичность объекта никогда не меняется после его создания; можно считать её адресом объекта в памяти). Там же отдельной пометкой о реализации: «For CPython, id(x) is the memory address where x is stored» (В CPython id(x) — это адрес в памяти, по которому хранится x).https://docs.python.org/3.14/reference/datamodel.html#objects-values-and-types
- Встроенные функции — id()Официальная документация. Ключевая оговорка урока про сравнение id, дословно: «This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value» (Это целое число, гарантированно уникальное и постоянное для данного объекта в течение его жизни. Два объекта с непересекающимися временами жизни могут иметь одно и то же значение id()). Отсюда и id([]) == id([]), дающее True.https://docs.python.org/3.14/library/functions.html#id
- Справочник языка — проверка вхожденияОфициальная документация. Объяснение, почему nan находится в списке, содержащем его же. Справочник даёт точную эквивалентность: «For container types such as list, tuple, set, frozenset, dict, or collections.deque, the expression x in y is equivalent to any(x is e or x == e for e in y)» (Для контейнерных типов, таких как list, tuple, set, frozenset, dict или collections.deque, выражение x in y эквивалентно any(x is e or x == e for e in y)). Тождество проверяется ПЕРВЫМ — это записано прямо в порядке операндов or.https://docs.python.org/3.14/reference/expressions.html#membership-test-operations
- sys.intern — таблица интернированных строкОфициальная документация. Что делает функция и зачем: «Enter string in the table of “interned” strings and return the interned string — which is string itself or a copy» (Помещает строку в таблицу „интернированных“ строк и возвращает интернированную строку — саму эту строку либо её копию), и там же о выгоде: «the key comparisons (after hashing) can be done by a pointer compare instead of a string compare» (сравнение ключей (после хеширования) может выполняться сравнением указателей вместо сравнения строк). Оттуда же факт, объясняющий половину наблюдений урока: «Normally, the names used in Python programs are automatically interned» (Обычно имена, используемые в программах на Python, интернируются автоматически).https://docs.python.org/3.14/library/sys.html#sys.intern
- PEP 683 — Immortal Objects, Using a Fixed RefcountPEP. Эрик Сноу и Эдди Элизондо, принят в 3.12. Отсюда взято, что None, True, False и малые числа перестают считать ссылки: у вечного объекта счётчик не меняется вовсе, и sys.getrefcount возвращает служебную константу. На проверку тождества это не влияет никак — PEP нужен ровно затем, чтобы объяснить, почему это число нельзя читать как «сколько ссылок».https://peps.python.org/pep-0683/
- Что нового в Python 3.8 — предупреждение об is с литераломОфициальная документация. Источник версии для предупреждения и его формулировка причины: «The compiler now produces a SyntaxWarning when identity checks (is and is not) are used with certain types of literals (e.g. strings, numbers). These can often work by accident in CPython, but are not guaranteed by the language spec» (Компилятор теперь выдаёт SyntaxWarning, когда проверки тождества (is и is not) применяются к литералам некоторых типов (например, строкам, числам). В CPython они часто работают случайно, но спецификацией языка не гарантированы). Формулировка «работают случайно» — ровно то, о чём этот урок.https://docs.python.org/3/whatsnew/3.8.html