Kate Murphy Twitter Github RSS

Weird Python Integers

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

Editing Integers?

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.