Подтвердить что ты не робот

В Python, когда два объекта одинаковы?

Кажется, что 2 is 2 и 3 is 3 всегда будут истинными в python, и вообще любая ссылка на целое будет такой же, как любая другая ссылка на одно и то же целое число. То же самое происходит с None (т.е. None is None). Я знаю, что это не происходит с пользовательскими типами или изменяемыми типами. Но иногда он терпит неудачу и на неизменяемых типах:

>>> () is ()
True
>>> (2,) is (2,)
False

То есть: две независимые конструкции пустого кортежа дают ссылки на один и тот же объект в памяти, но две независимые конструкции одинаковых одномерных (неизменяемых) кортежей элементов создают два одинаковых объекта. Я тестировал, а frozenset работал так же, как и кортежи.

Что определяет, будет ли объект дублироваться в памяти или будет иметь один экземпляр с большим количеством ссылок? Это зависит от того, является ли объект "атомарным" в некотором смысле? Различается ли она в соответствии с реализацией?

4b9b3361

Ответ 1

Python имеет некоторые типы, которые он гарантирует, будет иметь только один экземпляр. Примерами этих экземпляров являются None, NotImplemented и Ellipsis. Это (по определению) синглтоны, и поэтому такие вещи, как None is None, возвращаются True, потому что нет способа создать новый экземпляр NoneType.

Он также предоставляет несколько двухстрочных кнопок 1True, False 2. Все ссылки на True указывают на один и тот же объект. Опять же, это потому, что нет способа создать новый экземпляр bool.

Все вышеперечисленные вещи гарантируются языком python. Однако, как вы заметили, существуют некоторые типы (все неизменные), которые хранят некоторые экземпляры для повторного использования. Это допускается языком, но разные реализации могут использовать это пособие или нет - в зависимости от их стратегий оптимизации. Некоторые примеры, попадающие в эту категорию, представляют собой малые целые числа (-5 → 255), пустые tuple и пустые frozenset.

Наконец, Cpython intern некоторые неизменяемые объекты во время разбора...

например. если вы запустите следующий script с Cpython, вы увидите, что он возвращает True:

def foo():
    return (2,)

if __name__ == '__main__':
    print foo() is foo()

Это кажется действительно странным. Трюк, который играет Cpython, заключается в том, что всякий раз, когда он создает функцию foo, он видит строковый литерал, который содержит другие простые (неизменные) литералы. Вместо того, чтобы создавать этот кортеж (или его эквиваленты) снова и снова, python просто создает его один раз. Там нет опасности того, что этот объект будет изменен, поскольку вся сделка является неизменной. Это может быть большой победой для производительности, когда один и тот же замкнутый цикл вызывается снова и снова. Маленькие струны также интернированы. Настоящая победа здесь в словарных поисках. Python может выполнить (сверкающе быстро) сравнение указателей, а затем вернуться к более медленным сравнениям строк при проверке хеш-коллизий. Так как большая часть python построена на словарных поисках, это может быть большой оптимизацией для языка в целом.


1 Возможно, я только что составил это слово... Но, надеюсь, вы получите идею...
2 При нормальных обстоятельствах вам не нужно проверять, является ли объект ссылкой на True - обычно вам просто нужно, чтобы объект был "правдивым" - например, если if some_instance: ... выполнит ветвь. Но я поставил это здесь только для полноты.


Обратите внимание, что is может использоваться для сравнения вещей, которые не являются одиночными. Одним из распространенных способов использования является создание контрольного значения:

sentinel = object()
item = next(iterable, sentinel)
if items is sentinel:
   # iterable exhausted.

Или:

_sentinel = object()
def function(a, b, none_is_ok_value_here=_sentinel):
    if none_is_ok_value_here is sentinel:
        # Treat the function as if `none_is_ok_value_here` was not provided.

Мораль этой истории - всегда говорить, что вы имеете в виду. Если вы хотите проверить, является ли значение другим значением, используйте оператор is. Если вы хотите проверить, равно ли значение другому значению (но, возможно, иному), используйте ==. Для получения более подробной информации о разнице между is и == (и когда использовать их) обратитесь к одному из следующих сообщений:


Добавление

Мы говорили об этих деталях реализации CPython, и мы утверждаем, что они оптимизированы. Было бы неплохо попытаться измерить только то, что мы получаем от всех этих оптимизаций (кроме немного добавленной путаницы при работе с оператором is).

Строка "интернирование" и поиск в словарях.

Здесь небольшой script, который вы можете запустить, чтобы узнать, насколько быстрее используются словарные словари, если вы используете ту же строку для поиска значения вместо другой строки. Заметьте, я использую термин "интернированный" в именах переменных. Эти значения необязательно интернированы (хотя они могут быть). Я просто использую это, чтобы указать, что "интернированная" строка является строкой в ​​словаре.

import timeit

interned = 'foo'
not_interned = (interned + ' ').strip()

assert interned is not not_interned


d = {interned: 'bar'}

print('Timings for short strings')
number = 100000000
print(timeit.timeit(
    'd[interned]',
    setup='from __main__ import interned, d',
    number=number))
print(timeit.timeit(
    'd[not_interned]',
    setup='from __main__ import not_interned, d',
    number=number))


####################################################

interned_long = interned * 100
not_interned_long = (interned_long + ' ').strip()

d[interned_long] = 'baz'

assert interned_long is not not_interned_long
print('Timings for long strings')
print(timeit.timeit(
    'd[interned_long]',
    setup='from __main__ import interned_long, d',
    number=number))
print(timeit.timeit(
    'd[not_interned_long]',
    setup='from __main__ import not_interned_long, d',
    number=number))

Точные значения здесь не должны иметь большого значения, но на моем компьютере короткие строки показывают примерно 1 часть в 7 быстрее. Длинные строки почти в 2 раза быстрее (потому что сравнение строк занимает больше времени, если строка имеет больше символов для сравнения). Различия не столь впечатляющие на python3.x, но они все еще определенно существуют.

Кортеж "интернирование"

Здесь небольшой script вы можете играть с:

import timeit

def foo_tuple():
    return (2, 3, 4)

def foo_list():
    return [2, 3, 4]

assert foo_tuple() is foo_tuple()

number = 10000000
t_interned_tuple = timeit.timeit('foo_tuple()', setup='from __main__ import foo_tuple', number=number)
t_list = (timeit.timeit('foo_list()', setup='from __main__ import foo_list', number=number))

print(t_interned_tuple)
print(t_list)
print(t_interned_tuple / t_list)
print('*' * 80)


def tuple_creation(x):
    return (x,)

def list_creation(x):
    return [x]

t_create_tuple = timeit.timeit('tuple_creation(2)', setup='from __main__ import tuple_creation', number=number)
t_create_list = timeit.timeit('list_creation(2)', setup='from __main__ import list_creation', number=number)
print(t_create_tuple)
print(t_create_list)
print(t_create_tuple / t_create_list)

Это немного сложнее во времени (и я рад принять любые лучшие идеи, как это сделать в комментариях). Суть в том, что в среднем (и на моем компьютере) кортеж занимает около 60%, чтобы создать его как список. Однако foo_tuple() занимает в среднем около 40% времени, которое занимает foo_list(). Это показывает, что мы действительно получаем немного ускорения от этих стажеров. Сбережения времени, похоже, увеличиваются по мере увеличения кортежа (создание более длинного списка занимает больше времени. Коррекция "создание" занимает постоянное время с момента ее создания).

Также обратите внимание, что я назвал это "интернированием". На самом деле это не (по крайней мере, не в том же смысле, что строки интернированы). Мы видим разницу в этом простом script:

def foo_tuple():
    return (2,)

def bar_tuple():
    return (2,)

def foo_string():
    return 'foo'

def bar_string():
    return 'foo'

print(foo_tuple() is foo_tuple())  # True
print(foo_tuple() is bar_tuple())  # False

print(foo_string() is bar_string())  # True

Мы видим, что строки действительно "интернированы". Различные вызовы, использующие одну и ту же буквенную нотацию, возвращают один и тот же объект. Кортеж "интернирование" кажется специфичным для одной строки.

Ответ 2

Это зависит от реализации.

CPython кэширует некоторые неизменяемые объекты в памяти. Это справедливо для "малых" целых чисел, таких как 1 и 2 (от -5 до 255, как отмечено в комментариях ниже). CPython делает это по соображениям производительности; малые целые числа обычно используются в большинстве программ, поэтому он сохраняет память только для создания одной копии (и она безопасна, поскольку целые числа неизменяемы).

Это также относится к объектам "singleton", таким как None; в любой момент времени существует только один None.

Другие объекты (например, пустой кортеж, ()) могут быть реализованы как одиночные, или они могут не быть.

В общем, вы не должны предполагать, что неизменяемые объекты будут реализованы таким образом. CPython делает это по соображениям производительности, но другие реализации могут и не быть, и CPython может даже перестать делать это в какой-то момент в будущем. (Единственное исключение может быть None, так как x is None является общей идиомой Python и может быть реализована в разных интерпретаторах и версиях.)

Обычно вы хотите использовать == вместо is. Оператор Python is не используется часто, за исключением случаев, когда проверяется, есть ли переменная None.