Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

Присваивание целых (int) в Питоне

Допустим, есть следующий код:
a = 32 b = a
print(a is b)
Как сделать так, чтобы b становилось ссылкой на новое значение в памяти, а не на то значение, на которое ссылается a, т.е. как сделать так, чтобы операция print(a is b) напечатала False?


Ответ

b = a заставляет b имя ссылаться на тот же объект что и a имя.
Операция a is b проверяет указывают ли оба имени на один и тот же объект, поэтому всегда всегда a is b будет True после a = b в Питоне.
Если вы хотите, чтобы имя b ссылалось на объект отличный от a, то просто присвойте любой другой объект, например, b = None для примера в вопросе или можно вообще удалить имя del b (при попытке использования после этого исключение возникнет).

Если вы хотите, чтобы a и b были равны (a == b == 32), но были бы разными объектами (a is not b), то ответ может зависеть от конкретной реализации Питона.
Например, в CPython маленькие целые числа кэшируются поэтому 32 is (16*2) is (31+1)... то есть существует только один объект, который представляет число 32 в СPython. Целые числа неизменяемы в Питоне, поэтому арифметика не изменится от того один и тот же объект для 32 возвращается каждый раз или разные.
Нужно обратить внимание, что не следует оператор is использовать для сравнения чисел. Так как разные объекты могут иметь одно значение (быть равными), например, 1000 == (999+1), но 1000 is not (999+1) (в данном случае (999+1) возвращает новый объект, отличный от объекта, созданного для 1000 константы). is можно использовать для сравнения с singleton—объект, который в единственном экземпляре существует, такой как None. Питон не обещает, что объекты, представляющие числа, существуют в единственном экземпляре, поэтому используйте == для сравнения целых чисел.

Есть несколько решений буквальной задачи (a == b == 32 and a is not b), если её рассматривать как загадку (не для использования на практике).
Чтобы обойти кэширование маленьких чисел, можно создать производный от int класс:
>>> a = 32 >>> class Int(int): ... pass ... >>> b = Int(a) >>> a == b == 32 and a is not b True
Или использовать float или любой другой тип, который можно с целыми сравнивать:
>>> b = 32. >>> a == b == 32 and a is not b True
Наконец, если вы хотите, чтобы type(a) == type(b) == int, где int это встроенный PyLongObject тип, то возможно сделать злую вещь и поменять значение известной константы 42
>>> import ctypes >>> import sys >>> a = 32 >>> b = 42 >>> (ctypes.c_ubyte * sys.getsizeof(b)).from_address(id(b))[-4] = a >>> a == b == 32 and a is not b True >>> type(a) == type(b) == int True >>> (32 + 10) == 32 True
id(b) возвращает адрес объекта в памяти, представляющего 42, в CPython sys.getsizeof(b) возвращает размер объекта b в байтах (ctypes.c_ubyte * sys.getsizeof(b)) задаёт тип массива байтов, который может вместить b объект (unsigned char[28]) .from_address() создаёт массив из памяти, начиная с адреса, где b объект расположен arr[-4] = a присваивает четвёртому с конца байту значение 32. Что имеет эффект изменения константы 42 (связано с тем как _longobject в памяти представлен (единственная цифра/digit меняется), в частности это говорит, что python3 команда использует PYLONG_BITS_IN_DIGIT == 30 , что sys.int_info подтверждает и порядок байтов от младшего к старшему на моей машине, что sys.byteorder также подтверждает).
Код был вдохновлён статьёй: «...Как избавиться от коллеги, пишущего на Питоне».

Комментариев нет:

Отправить комментарий