Python Objects! Mutable or Immutable?


Unlike top-down programming with languages such as C. Python is an object oriented programming language, and EVERYTHING is an object. But some objects are mutable and some aren’t.

Note that values and objects are not the same:

a = "zelda"
b = "zelda"

The == operator checks if the two variables have the same values : a == b returns True

>>> a == b

The is operator checks if the two variables are the same object: a is b returns True

>>> a is b

PyObject, which is a super class all data types inherit from.

What is PyObject?

typedef struct _object {
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
  1. _PyObject_HEAD_EXTRA: line 70–72
/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA
struct _object *_ob_next;
struct _object *_ob_prev;

2. Py_ssize_t ob_refcnt: object reference count

This variable keeps track of the number of PyObject allocated.

3. struct _typeobject *ob_type [typestruct.h]

The _typeobject is really important, it handles all the behaviors of a python object: allocation, deallocation, setters/getters, subclassing objects, etc.

_typeobject structure provides a means of invoking up to (currently
 about) 41 special operators. So, for example, a routine can get an
 item from any object that provides sequence behavior. However, to
 use this mechanism, the programmer must make their code dependent on
 the current Python implementation. [abstract.h: line 39]

Source code: typestruct.h

typedef struct _typeobject {
char *tp_name; /* For printing, in format "<module>.<name>" */
int tp_basicsize, tp_itemsize; /* For allocation */
/* Methods to implement standard operations */
destructor tp_dealloc;
printfunc tp_print;
getattrfunc tp_getattr;
setattrfunc tp_setattr;
void *tp_reserved;
reprfunc tp_repr;
/* Method suites for standard classes */
PyNumberMethods *tp_as_number;
PySequenceMethods *tp_as_sequence;
PyMappingMethods *tp_as_mapping;
/* More standard operations (here for binary compatibility) */
hashfunc tp_hash;
ternaryfunc tp_call;
reprfunc tp_str;
getattrofunc tp_getattro;
setattrofunc tp_setattro;
/* Functions to access object as input/output buffer */
PyBufferProcs *tp_as_buffer;
/* Flags to define presence of optional/expanded features */
long tp_flags;
char *tp_doc; /* Documentation string */
/* call function for all accessible objects */
traverseproc tp_traverse;
/* delete references to contained objects */
inquiry tp_clear;
/* rich comparisons */
richcmpfunc tp_richcompare;
/* weak reference enabler */
long tp_weaklistoffset;
/* Iterators */
getiterfunc tp_iter;
iternextfunc tp_iternext;
/* Attribute descriptor and subclassing stuff */
struct PyMethodDef *tp_methods;
struct PyMemberDef *tp_members;
struct PyGetSetDef *tp_getset;
struct _typeobject *tp_base;
PyObject *tp_dict;
descrgetfunc tp_descr_get;
descrsetfunc tp_descr_set;
long tp_dictoffset;
initproc tp_init;
allocfunc tp_alloc;
newfunc tp_new;
freefunc tp_free; /* Low-level free-memory routine */
inquiry tp_is_gc; /* For PyObject_IS_GC */
PyObject *tp_bases;
PyObject *tp_mro; /* method resolution order */
PyObject *tp_cache;
PyObject *tp_subclasses;
PyObject *tp_weaklist;
destructor tp_del;
/* Type attribute cache version tag. Added in version 2.6 */
unsigned int tp_version_tag;
destructor tp_finalize;
} PyTypeObject;

All objects in python are created from the C, in the struct _object

id() and type()

id: Return the “identity” of an object. This is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects will not have the same id. Essentially, the id() returns the memory address of an object.

Source: bltinmodule.c line: 996

static PyObject *
builtin_id(PyObject *self, PyObject *v)
return PyLong_FromVoidPtr(v);
“id(object) -> integer\n\
Return the identity of an object. This is guaranteed to be unique among\n\
simultaneously existing objects. (Hint: it’s the object’s memory address.)”);

PyLong_FromVoidPtr returns a Python long object from a void pointer. So, in CPython, this value is the address of the object.

type() is another builtin that determines what class an object belongs to.

Using id() and type()

type: returns the type of an object
>>> type(True)
<type ‘bool’>
>>> type(“hello”)
<type ‘str’>
>>> a = "hello"
>>> id(a)
>>> b = "hello"
>>> id(b)
140122927994608 #b is the same object as a
>>> a = [1, 2, 3]
>>> b = a
>>> id(a)
>>> id(b)
140122927947144 #b is referencing the same object as a
>>> a = [1, 2, 3]
>>> b = a + [4]
>>> id(a)
>>> id(b)
140122927947072 # b:mutable, adding [4] refers to another object

In python, the following are some mutable objects:

  • list, dict, set, bytearray, user-defined classes (unless specifically made immutable)

However, the following are immutable objects:

  • int, float, decimal, complex, bool, string, tuple, range, frozenset, bytes

Mutable objects

A mutable object is an object whose contents can change. When you pass a mutable object into a function, the function can change the object’s contents too.

>>> a = [1, 2, 3]
>>> a.append(4)
>>> a
[1, 2, 3, 4]
>>> def spam(x):
... x.append(5)
>>> a
[1, 2, 3, 4]

Immutable objects

As opposed to mutable objects, immutable objects’ contents cannot be changed. However, you can always reassign a variable to something else. If x contains 3, you can compute x + 1 to get a new number, 4, and assign that to x. But that doesn't change the value of 3. Note: the id is different for a and b.

>>> a = 3
>>> b = a
>>> id(a)
>>> id(b)
>>> b = b + 1
>>> id(b)
>>> b

The value of the variable changes, but it changes by changing what the variable refers to. A mutable type can change that way, and it can also change “in place”.

Memory representation, preallocated integers

In python, an array of 261 integers is preallocate, fromNSMALLNEGINTS(-5) to NSMALLPOSINTS (+257), so the integers are already set in memory and thus immutable.

So we have x = 3, x is pointing to the memory address of 3. As noted above, integers are immutable types; and that the actual number cannot be changed. However, variables can point to it and point to another number.

When you add x + 1, the x is pointing to a different address 4.

You can verify that 3 and 4 are indeed in different memory location with id()

>>> id(3)
>>> id(4)


Since variables refer to objects, if we assign one variable to another, both variables refer to the same object

>>> a = [1, 2, 3]
>>> b = a
>>> a is b

How arguments are passed to functions and what does that imply for mutable and immutable objects

Since lists are mutable, it is often desirable to traverse a list, modifying each of its elements. The following squares all the numbers from 1 to 5:

numbers = [1, 2, 3, 4, 5]
for index in range(len(numbers)):
numbers[index] = numbers[index]**2

Passing a list as an argument actually passes a reference to the list, not a copy of the list. Since lists are mutable changes made to the parameter change the argument as well. For example, the function below takes a list as an argument and multiplies each element in the list by 2:

Why does it matter and how differently does Python treat mutable and immutable objects

a_str = ""
s in a_str:
a_str +=

This is very inefficient, because strings are immutable. Concatenating two strings together actually creates another string which is the combination of the previous two. If you are iterating a lot and building a large string, you will waste a lot of memory creating objects.

A more efficient way:

a_list = []
elem in a_list:

By using a list, which is a mutable object, this cuts down on the total number of objects needed to be allocated.