How does passing parameters to functions in Python actually work?
Introduction
A very common source of problems when you start writing classes, methods and functions in Python is the confusion over what is happening when you call a function, for example. It is worth spending a little time understanding the process.
Calling the fight function
Here is the function we wrote called fight:
What happened when we instantiated a monster using monster1 = monster ('Gregor', 11, 7, 3) and monster2 = monster ('Smog', 1, 9, 8) and what exactly is going on when call the fight function with fight (monster1, monster2).? To understand the answers to this question, we have to know how Python stores objects we create in memory and how variables and their pointers work. It's not a difficult concept and once you understand how it works, you will have far fewer errors in your code!
Instantiating a monster
When you instantiated a monster:
- Python created a space to store the monster's details in the computer's RAM, its memory.
- It stored the name that you said you wanted to call the object in a table.
- It also stores the RAM address of the object's actual data in RAM in the table, and this is called a pointer.
We can represent this with a diagram.
Python decides where in memory to put the monster and you never need to know. All you need to know is what the monster's name is. Whenever you need an attribute, for example, monster1.strength, you use the dot notation. Python will look up the name in its table, get the pointer, follow it to the correct memory location, get the data and use it.
Calling a function
If you call a function and pass it some data, Python will create an area in its table for the names of the variables that are being used in the function, and then create pointers that point to the same data as the variables we are passing. For example, we called the function using this fight (monster1, monster2). The function header is def fight (mon1, mon2). The two variables get passed to the function in the same order as the variables in the function header. This can better be seen with an updated diagram.
So now, if we increase the strength of Gregor inside the function using, for example mon1.strength = mon1.strength + 2, we alter the contents of memory. The pointer from both monster1 and mon1 are still pointing to the same area of memory, so any changes we make using mon1 will be reflected when we use monster1. This is because they are just pointers to one set of data in the memory.
Rebinding a variable
We know that any modifications we make to mon1 will be reflected in monster1, because they are just pointers to the same area of memory. But what if we don't modify mon1 but assign it to a different value? For example, what if we have an instruction at the end of the fight function that said this: mon1 = "Hello!" This is an example of rebinding a variable. Python will store Hello! in its memory and then move mon1's pointer so that it is pointing to it. Let's see this as a diagram:
You can see now that if you printed out mon1, you would get "Hello!" but monster1 is unchanged.
The important idea here is that Python works with variables and pointers to memory. When you pass a variable to a function, a duplicate variable is made. When you modify the duplicate, you are modifying an area in memory so both the original variable and the duplicate point to the same area of memory. If you rebind the duplicate, however, the pointers will then point to different areas in memory.
Use a visualiser
This is an important idea in programming and program languages handle this in slightly different ways. When you have to pass pieces of data (called parameters) to functions, keep in mind how Python deals with the process. If you are still struggling with this idea, copy your code into www.pythontutor.com and run the visualiser so you can see what happens when the function is called.