Pointers in Python are basically variables that store the memory addresses of other objects. All languages store data, instructions, and programs in memory and therefore they are stored at specific memory locations. Knowing how pointers work helps you understand a lot about a language’s implementation details.
Some languages like C and C++ make pointers to be a “first class” object meaning they give you many primitives and functions to work and reason with pointers. Languages like Python, on the other hand, abstract away details of pointers and a regular programmer will not need to “peek under the hood”. Nevertheless, Python provides many functions that will help us understand how the language manages its memory.
Consider the following simple program:
This produces the output:
The id() function
Let us now try this:
(If you try this, the second number that is printed might be different for you.) The function id(pi) returns the object’s memory address; here, the object is the variable “pi” and therefore it is returning the address where this object is stored.
Let us change the value of pi and redo the above statements:
Notice two important differences. First the second line has printed a new address (not the address we saw earlier). This is because each time we run the program it is starting a fresh execution and allocating a fresh chunk of memory and therefore is using a new address (namely, 140196188842032) and not the old address (140067448715312). But more importantly, note that when I reassign pi to a new value (3.1415 instead of 3.14) and I print the id(pi) again, I get a new value! This new memory location is at: 140196122305712.
The reason this is happening is that variables like “pi” (in general, all integer, float, string, boolean variables) are immutable, meaning they cannot be changed. So when we re-assigned the value of pi we have essentially created a new object and made the variable “pi” point to it. Even though it might seem as if we have changed the value of pi (contradicting the property of immutability), we have in reality created a new object and made pi point to it. This will happen even if you do some operations on the variable, like so:
Note that the two addresses are different.
Let us consider the following program:
This produces the following output:
Note that in this case the two addresses are the same. The memory location holding the number of interest (i.e., 3.14) is the same but two different variables point to it. If you were to re-assign y to something else, id(x) would give the same value but id(y) would be different:
Note that id(x) is the same in both printings (before and after reassignment of y) but id(y) has changed (after reassignment).
Mutable data types
Let us try the following piece of code with lists:
This gives the output:
Note what has happened. The list has been modified but the address of the list stays the same! This is because lists are mutable objects in Python. You can modify lists and change them and this is why the memory address stays the same. Let us try a few more operations to modify the list:
As expected the memory address does not change.
What are mutable data types in Python?
Lists, sets, and dictionaries are mutable in Python. Most other primitive data types, like integers, strings, floating point numbers, boolean, tuples, and complex numbers are immutable.
The is operator
Python has another useful function to check if two objects have the same memory address. Instead of printing the memory addresses and comparing them the is inflix operator compares two objects and determines if they share the same memory address. For instance:
Let us now try:
Wow - how can that be? I only modified farmanimals! Let us print the animals list and verify:
This is because when we copy mutable objects (using our assignment statement above), the copying is done by reference (this is called “Copy by Reference”). As a result, a change in one object is reflected in the other object as well.
On the other hand when we copy immutable objects (like strings, variables) and change one of them a new object is created (as we saw using the id() function).
Note that the id() function is different from the == operator (the latter compares values whereas the former compares memory locations). For instance, consider the following code:
This means the addresses as well (or, thus,) the contents of these addresses are the same. On the other hand, consider the following code:
This is because in the second line, when we created the farmanimals variable we initialized it separately, not with reference to the animals variable. As a result, the memory locations are different but the contents are the same.
So what have we learned in this blog post? Python pointers are useful to peek behind the scenes at how (and where) objects are allocated and what happens as these objects are modified over the course of a program. There are Python modules like ctypes that enable you to more directly work with pointers similar to languages like C/C++ but that is beyond the scope of this article. Also read about Python memory errors.
Kodeclik is an online coding academy for kids and teens to learn real world programming. Kids are introduced to coding in a fun and exciting way and are challeged to higher levels with engaging, high quality content.