Watch Now This tutorial has a related video course created by the Real Python team. Watch it together with the written tutorial to deepen your understanding: Pointers and Objects in Python
If you’ve ever worked with lower level languages like C or C++, then you’ve probably heard of pointers. Pointers allow you to create great efficiency in parts of your code. They also cause confusion for beginners and can lead to various memory management bugs, even for experts. So where are they in Python, and how can you simulate pointers in Python?
Pointers are widely used in C and C++. Essentially, they are variables that hold the memory address of another variable. For a refresher on pointers, you might consider checking out this overview on C Pointers.
In this article, you’ll gain a better understanding of Python’s object model and learn why pointers in Python don’t really exist. For the cases where you need to mimic pointer behavior, you’ll learn ways to simulate pointers in Python without the memory-management nightmare.
In this article, you’ll:
- Learn why pointers in Python don’t exist
- Explore the difference between C variables and Python names
- Simulate pointers in Python
- Experiment with real pointers using
ctypes
Note: In this article, “Python” will refer to the reference implementation of Python in C, otherwise known as CPython. As the article discusses some internals of the language, these notes are true for CPython 3.7 but may not be true in future or past iterations of the language.
Free Download: Get a sample chapter from Python Tricks: The Book that shows you Python’s best practices with simple examples you can apply instantly to write more beautiful + Pythonic code.
Why Doesn’t Python Have Pointers?
The truth is that I don’t know. Could pointers in Python exist natively? Probably, but pointers seem to go against the Zen of Python. Pointers encourage implicit changes rather than explicit. Often, they are complex instead of simple, especially for beginners. Even worse, they beg for ways to shoot yourself in the foot, or do something really dangerous like read from a section of memory you were not supposed to.
Python tends to try to abstract away implementation details like memory addresses from its users. Python often focuses on usability instead of speed. As a result, pointers in Python don’t really make sense. Not to fear though, Python does, by default, give you some of the benefits of using pointers.
Understanding pointers in Python requires a short detour into Python’s implementation details. Specifically, you’ll need to understand:
- Immutable vs mutable objects
- Python variables/names
Hold onto your memory addresses, and let’s get started.
Objects in Python
In Python, everything is an object. For proof, you can open up a REPL and explore using isinstance():
>>> isinstance(1, object)
True
>>> isinstance(list(), object)
True
>>> isinstance(True, object)
True
>>> def foo():
... pass
...
>>> isinstance(foo, object)
True
This code shows you that everything in Python is indeed an object. Each object contains at least three pieces of data:
- Reference count
- Type
- Value
The reference count is for memory management. For an in-depth look at the internals of memory management in Python, you can read Memory Management in Python.
The type is used at the CPython layer to ensure type safety during runtime. Finally, there’s the value, which is the actual value associated with the object.
Not all objects are the same though. There is one other important distinction you’ll need to understand: immutable vs mutable objects. Understanding the difference between the types of objects really helps clarify the first layer of the onion that is pointers in Python.
Immutable vs Mutable Objects
In Python, there are two types of objects:
- Immutable objects can’t be changed.
- Mutable objects can be changed.
Understanding this difference is the first key to navigating the landscape of pointers in Python. Here’s a breakdown of common types and whether or not they are mutable or immutable:
| Type | Immutable? |
|---|---|
int |
Yes |
float |
Yes |
bool |
Yes |
complex |
Yes |
tuple |
Yes |
frozenset |
Yes |
str |
Yes |
list |
No |
set |
No |
dict |
No |
As you can see, lots of commonly used primitive types are immutable. You can prove this yourself by writing some Python. You’ll need a couple of tools from the Python standard library:
id()returns the object’s memory address.isreturnsTrueif and only if two objects have the same memory address.
Once again, you can use these in a REPL environment:
>>> x = 5
>>> id(x)
94529957049376
In the above code, you have assigned the value 5 to x. If you tried to modify this value with addition, then you’d get a new object:
>>> x += 1
>>> x
6
>>> id(x)
94529957049408
Even though the above code appears to modify the value of x, you’re getting a new object as a response.
The str type is also immutable:
>>> s = "real_python"
>>> id(s)
140637819584048
>>> s += "_rocks"
>>> s
'real_python_rocks'
>>> id(s)
140637819609424
Again, s ends up with a different memory addresses after the += operation.
Bonus: The += operator translates to various method calls.
For some objects like list, += will translate into __iadd__() (in-place add). This will modify self and return the same ID. However, str and int don’t have these methods and result in __add__() calls instead of __iadd__().
For more detailed information, check out the Python data model docs.
Trying to directly mutate the string s results in an error:
>>> s[0] = "R"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
The above code fails, and Python indicates that str doesn’t support this mutation, which is in line with the definition that the str type is immutable.
Contrast that with a mutable object, like list:
>>> my_list = [1, 2, 3]
>>> id(my_list)
140637819575368
>>> my_list.append(4)
>>> my_list
[1, 2, 3, 4]
>>> id(my_list)
140637819575368
This code shows a major difference in the two types of objects. my_list has an id originally. Even after 4 is appended to the list, my_list has the same id. This is because the list type is mutable.
Another way to demonstrate that the list is mutable is with assignment:
>>> my_list[0] = 0
>>> my_list
[0, 2, 3, 4]
>>> id(my_list)
140637819575368
In this code, you mutate my_list and set its first element to 0. However, it maintains the same id even after this assignment. With mutable and immutable objects out of the way, the next step on your journey to Python enlightenment is understanding Python’s variable ecosystem.
Understanding Variables
Python variables are fundamentally different than variables in C or C++. In fact, Python doesn’t even have variables. Python has names, not variables.
This might seem pedantic, and for the most part, it is. Most of the time, it’s perfectly acceptable to think about Python names as variables, but understanding the difference is important. This is especially true when you’re navigating the tricky subject of pointers in Python.
To help drive home the difference, you can take a look at how variables work in C, what they represent, and then contrast that with how names work in Python.
Variables in C
Let’s say you had the following code that defines the variable x:
int x = 2337;
This one line of code has several, distinct steps when executed:
- Allocate enough memory for an integer
- Assign the value
2337to that memory location - Indicate that
xpoints to that value
Shown in a simplified view of memory, it might look like this:

Here, you can see that the variable x has a fake memory location of 0x7f1 and the value 2337. If, later in the program, you want to change the value of x, you can do the following:
x = 2338;
The above code assigns a new value (2338) to the variable x, thereby overwriting the previous value. This means that the variable x is mutable. The updated memory layout shows the new value:

Notice that the location of x didn’t change, just the value itself. This is a significant point. It means that x is the memory location, not just a name for it.
Another way to think of this concept is in terms of ownership. In one sense, x owns the memory location. x is, at first, an empty box that can fit exactly one integer in which integer values can be stored.
When you assign a value to x, you’re placing a value in the box that x owns. If you wanted to introduce a new variable (y), you could add this line of code: