Question:
Let's say you have the following code:
a = 32
b = a
print(a is b)
How to make b become a reference to a new value in memory, and not to the value that a refers to, i.e. how to make the operation print(a is b)
print False
?
Answer:
b = a
makes b
name refer to the same object as a
name.
The a is b
operation checks if both names point to the same object, so a is b
will always always be True
after a = b
in Python.
If you want the name b
refer to an object other than a
, then just assign any other object, for example, b = None
for the example in the question, or you can delete the name del b
altogether (if you try to use it, an exception will be thrown after that).
If you want a
and b
be equal ( a == b == 32
), but different objects ( a is not b
), then the answer may depend on the specific Python implementation.
For example, in CPython, small integers are cached, so 32 is (16*2) is (31+1)
… that is, there is only one object that represents 32
in CPython. Integers are immutable in Python, so arithmetic won't change whether the same object for 32
returned each time or different.
Note that you should not use the is
operator to compare numbers. Since different objects can have the same value (be equal), for example 1000 == (999+1)
, but 1000 is not (999+1)
(in this case, (999+1)
returns a new object different from the object, created for 1000
constants). is
can be used to compare to a singleton — an object that only exists, such as None
. Python does not promise that there is only one instance of objects representing numbers, so use ==
to compare integers.
There are several solutions to the literal problem ( a == b == 32 and a is not b
) if viewed as a puzzle (not for practical use).
To bypass the caching of small numbers, you can create an int
derived class:
>>> a = 32
>>> class Int(int):
... pass
...
>>> b = Int(a)
>>> a == b == 32 and a is not b
True
Or use float
or any other type that can be compared with integers:
>>> b = 32.
>>> a == b == 32 and a is not b
True
Finally, if you want type(a) == type(b) == int
, where int
is the built-in PyLongObject
type, then it is possible to do the evil thing and change the value of the well-known constant 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)
returns the address of an object in memory representing42
in CPython-
sys.getsizeof(b)
returns the size of objectb
in bytes -
(ctypes.c_ubyte * sys.getsizeof(b))
specifies the type of byte array thatb
object can hold (unsigned char[28]
) -
.from_address()
creates an array from memory, starting at the address where theb
object is located -
arr[-4] = a
sets the fourth byte from the end to32
. Which has the effect of changing the constant42
(due to how_longobject
is represented in memory (the only / digit changes) , in particular, it says that thepython3
command usesPYLONG_BITS_IN_DIGIT == 30
, thatsys.int_info
confirms thesys.int_info
order of my machine, whichsys.byteorder
also confirms).
The code was inspired by the article: "… How to Get Rid of a Colleague Writing in Python" .