Note: all of this code was run on my machine using Python 3.6.1. Not everything will work the same if you test using Python 2.
>>> a = 42
>>> b = 42
>>> a is b
True
>>> a = 316
>>> b = 316
>>> a is b
False
That is suprising! It turns out that all “small integers” with the same value point to the same memory. We can use the Python built-in function id
which returns a value you can think of as a memory address to investigate.
>>> a = 128
>>> b = 256
>>> id(a)
4504844960
>>> id(b)
4504849056
>>> (id(a) - id(b)) / (a - b)
32.0
It looks like there is a table of tiny integers and each integer takes up 32 bytes.
>>> x = 1000
>>> y = 2000
>>> id(x)
4508143344
>>> id(y)
4508143312
>>> id(x) - id(y)
32
It looks like integers that aren’t in the small integers table also take up 32 bytes. The id
for these is way larger than for the small integers which means they are stored somewhere else.
>>> id(x) - id(256)
3294288
What happens if we change the value of an integer in this table? Python has a module called ctypes that can be misused to directly edit memory. (We could also use a debugger but this way all the examples are in Python.)
Note: this code is very platform dependent. If it doesn’t work you might be using Python 2 instead of Python 3. In Python 2 changing mutate_int
so that both instances of 24 are 16 may work.
>>> import ctypes
>>>
>>> def mutate_int(an_int, new_value):
... ctypes.memmove(id(an_int) + 24, id(new_value) + 24, 8)
...
>>> a_number = 7
>>> another_number = 7
>>> mutate_int(a_number, 13)
>>> a_number
13
>>> another_number
13
Not only have we changed a_number
and another_number
but all new references to 7
:
>>> for i in range(0, 10):
... print(i)
...
0
1
2
3
4
5
6
13
8
9
Even doing math with 7
no longer works correctly 🎉
>>> 7
13
>>> 6 + 1
13
>>> 7 + 1
14
>>> (7 + 1) - 1
13
>>> 7 * 2
26
>>> 0b1111 ^ 0b1000
13
P.S. You can read more about the table of small integers in the CPython source code.