Ответ 1
ТЛ; др:
В справочном руководстве говорится:
Блок представляет собой часть текста программы Python, которая выполняется как единое целое. Ниже приведены блоки: модуль, тело функции и определение класса. Каждая команда, введенная интерактивно, является блоком.
Вот почему в случае функции у вас есть кодовый блок single, который содержит одиночный объект для числового литерала
1000
, поэтому id(a) == id(b)
даст True
.
Во втором случае у вас есть два разных объекта кода, каждый со своим собственным другим объектом для литерала 1000
, поэтому id(a) != id(b)
.
Обратите внимание, что это поведение не проявляется только с литералами int
, вы получите похожие результаты, например, с литералами float
(см. здесь).
Конечно, сравнение объектов (за исключением явных тестов is None
) всегда должно выполняться с помощью оператора равенства ==
, а не is
.
Все изложенное здесь относится к самой популярной реализации Python, CPython. Другие реализации могут отличаться, поэтому при их использовании не следует делать никаких предположений.
Более длинный ответ:
Чтобы получить немного более четкое представление и дополнительно проверить это, казалось бы, странное поведение, мы можем смотреть прямо в объектах code
для каждого из этих с использованием модуля dis
.
Для функции func
:
Наряду со всеми другими атрибутами объектные объекты также имеют атрибут __code__
, который позволяет заглянуть в скомпилированный байт-код для этой функции. Используя dis.code_info
, мы можем получить красивое представление обо всех хранимых атрибутах в объекте кода для данной функции:
>>> print(dis.code_info(func))
Name: func
Filename: <stdin>
Argument count: 0
Kw-only arguments: 0
Number of locals: 2
Stack size: 2
Flags: OPTIMIZED, NEWLOCALS, NOFREE
Constants:
0: None
1: 1000
Variable names:
0: a
1: b
Нам интересна запись Constants
для функции func
. В нем мы видим, что мы имеем два значения: None
(всегда присутствует) и 1000
. У нас есть только одиночный int-экземпляр, который представляет константу 1000
. Это значение, которое a
и b
будут назначаться при вызове функции.
Доступ к этому значению легко через func.__code__.co_consts[1]
, и поэтому другой способ просмотра нашей оценки a is b
в функции будет таким:
>>> id(func.__code__.co_consts[1]) == id(func.__code__.co_consts[1])
Что, конечно, будет оцениваться как True
, потому что мы имеем в виду один и тот же объект.
Для каждой интерактивной команды:
Как отмечалось ранее, каждая интерактивная команда интерпретируется как единый кодовый блок: анализируется, компилируется и оценивается независимо.
Мы можем получить объекты кода для каждой команды через compile
встроенный:
>>> com1 = compile("a=1000", filename="", mode="single")
>>> com2 = compile("b=1000", filename="", mode="single")
Для каждого оператора присваивания мы получим аналогичный объект кода, который выглядит следующим образом:
>>> print(dis.code_info(com1))
Name: <module>
Filename:
Argument count: 0
Kw-only arguments: 0
Number of locals: 0
Stack size: 1
Flags: NOFREE
Constants:
0: 1000
1: None
Names:
0: a
Такая же команда для com2
выглядит одинаково, но имеет принципиальное отличие: каждый из объектов кода com1
и com2
имеет разные int-экземпляры, представляющие литерал 1000
. Вот почему в этом случае, когда мы делаем a is b
через аргумент co_consts
, мы фактически получаем:
>>> id(com1.co_consts[0]) == id(com2.co_consts[0])
False
Что согласуется с тем, что мы действительно получили.
Различные объекты кода, различное содержимое.
Примечание: Мне было любопытно, как именно это происходит в исходном коде, и после того, как он прорыт его, я верю, что нашел его.
В фазе компиляции объект co_consts
представлен объектом словаря. В compile.c
мы можем увидеть инициализацию:
/* snippet for brevity */
u->u_lineno = 0;
u->u_col_offset = 0;
u->u_lineno_set = 0;
u->u_consts = PyDict_New();
/* snippet for brevity */
Во время компиляции это проверяется на наличие уже существующих констант. См. @Раймонд Хеттингер ниже для более подробной информации.
Предостережение:
-
Приведенные в цепочку выражения будут проверяться на проверку идентичности
True
Теперь должно быть более понятно, почему именно следующее оценивается как
True
:>>> a = 1000; b = 1000; >>> a is b
В этом случае, объединив две команды присваивания, мы попросим интерпретатора скомпилировать эти вместе. Как и в случае для объекта функции, будет создан только один объект для литерала
1000
, что приведет к значениюTrue
при оценке. -
Выполнение на уровне модуля возвращает
True
снова:Как упоминалось ранее, в справочном руководстве указано, что:
... Ниже приведены блоки: модуль...
Итак, применяется одно и то же предположение: у нас будет один объект кода (для модуля), и поэтому в результате отдельные значения сохраняются для каждого другого литерала.
-
Тот же не применяется для объектов изменчивых:
Это означает, что если мы явно не инициализируем один и тот же изменяемый объект (например, с a = b = []), то идентификатор объектов никогда не будет равен, например:
a = []; b = [] a is b # always returns false
Опять же, в документации, это указано:
после a = 1; b = 1, a и b могут или не могут ссылаться на один и тот же объект со значением один, в зависимости от реализации, но после c = []; d = [], c и d гарантированно относятся к двум различным уникальным, вновь созданным пустым спискам.