Redefining the number 2 in Python

In Python it's possible to redefine some builtin values that shouldn't really be changed. For example True and False can be changed in Python versions 2.7 and lower. This is fixed in Python 3 and assignment to True or False raises: "SyntaxError: assigment to keyword" but it works on Python 2.7:

Python 2.7.3 (default, Apr 20 2012, 22:39:59) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> True,False = False,True #Exchange True and False
>>> id(True)
9071696
>>> id(False)
9071664
>>> 1==1
True
>>> id(1==1) #This returns the id of False, but interpreter printed True last line
9071664 
>>> (1==1)==True #True doesn't equal True
False

Because of some internal Python workings only Trues and Falses that are written by the programmer are exchanged. Trues and Falses returned by Python are exchanged but are printed as not exchanged, as can be seen on the program above.

True and False aren't the only builtin values that can be changed, because CPython caches small integers we can change values of the integers in the cache.

In Python 2.7 and lower Python integers are represented in the C code as PyIntObject that has following definition:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

PyObject_HEAD is a C macro that by default expands as:

Py_ssize_t ob_refcnt;
PyTypeObject *ob_type;

ob_refcnt is the reference count and ob_type is a pointer to a structure that is used describe Python built-in types.

Using the "ctypes" module we can change ob_ival field that stores the value of the integer. We can get the address of the integer with id() function and we know that because of the internal representation of the Python integer, ob_ivals offset from the start of the struct is sizeof(size_t)+sizeof(void *). Following code changes the ob_ival value of the cached integer 2 to 3:

import ctypes

value = 2
ob_ival_offset = ctypes.sizeof(ctypes.c_size_t) + ctypes.sizeof(ctypes.c_voidp)
ob_ival = ctypes.c_int.from_address(id(value)+ob_ival_offset)
ob_ival.value = 3
print 1+1

Because of the integer cache every time Python thinks that it's using the integer 2 it's actually using the value 3. In Python 2.7 this program prints 3 and crashes. Running this in the interactive shell works, but trying to use it afterwards might lead to interesting behavior if it needs the value 2.

This code doesn't work in Python 3.0 or newer versions, because in Python 3.0 int and long types were combined in the one single type and this changed the PyIntObject struct.

social