Python compiler handles function arguments slightly different than other popular programming languages like C
, C++
, and Java but much like Ruby. Python function arguments behavior is confusing when a “mutable” object, like Python dict
(dictionary), set
or list
, is involved in the function argument list. The behavior of Python function arguments or parameters is interesting and confusing for the beginners at the same time. It is believed, this strange behavior bites once a time to almost all the beginners of Python developers in their programming life.
You need to spare some time and read this complete article with examples. It is advised that you should also read some other related articles mentioned here to understand this behavior. This will definitely help you to avoid strange circumstances in programming due to this behavior of Python function argument.
How Variables are Stored in Python
First, you need to understand how Python stores variables before we go further. This will help you to understand the behavior of Python mutable and immutable function arguments. In Python, almost everything is an object. Numbers, strings, functions, classes, modules and even Python compiled code, all are objects.
Python treats all variables as references to the object. This means all variables store the memory address of the actual object. This concept is much like “Pointer
” in C
and C++
programming language. This means the address of the actual object is stored in the Python named variable, not the value itself.
Furthermore, immutable and mutable objects or variables are handled differently while working with function arguments. We will discuss them in below section.
In the following diagram, variables a
, b
and name
point to their memory locations where the actual value of the object is stored.
Immutable Objects or Variables in Python
In Python, immutable objects are those whose value cannot be changed in place after assignment or initialization. They allocate new memory whenever their value is changed. Examples of immutable data types or objects or variables in Python are numbers (integers
, floats
), strings
and tuples
.
In above diagram, the value of variable b
is changed from 11
to 8
after its assignment or initialization. b
is an integer and it is an immutable variable. Hence, new memory is allocated for it and old is discarded. In other words, you can never overwrite the value of immutable objects. Every time you change the value of an immutable object, a new object is created in result. Old object is discarded and memory is cleaned up by garbage collector automatically for later use. So in first look, this seems inefficient but in practical it is not. This technique is also used in other very popular platforms like Java and .NET.
Immutable Object or Variable Example with Explanation in Python
x = 10 print("Value of x:", x) print("ID of x before modification:", id(x)) # add 20 to the value of x x += 20 print("Value of x:", x) print("ID of x after modification:", id(x))
Result:
Value of x: 10
ID of x before modification: 1528383264
Value of x: 20
ID of x after modification: 1528383584
Explanation:
- As you can see memory address of
x
is changed after modification of value ofx
variable. This is clear that new memory location is allocated after modification of value of variablex
Mutable Objects or Variables in Python
In Python, list
, dict
and set
are examples of mutable data types. Their values can be changed in place after their assignment or creation. When the value of a mutable variable is changed its memory is not reallocated.
In the above diagram, individual string
objects in the my_list
are immutable but my_list
itself is mutable. If we add, remove or modify individual string elements in my_list
, the my_list
variable points to same object.
Mutable Object or Variable Example with Explanation in Python
my_list = ["apple", "pear"] print("my_list:", my_list) print("ID of x before modification:", id(my_list)) # append "banana" to my_list my_list.append("banana") print("my_list:", my_list) print("ID of x after modification:", id(my_list))
Result:
my_list: ["apple", "pear"]
ID of x before modification: 19925552
my_list: ["apple", "pear", "banana"]
ID of x after modification: 19925552
Explanation:
- As you can see, memory address of variable is not changed after modifying the object
my_list
- This has been discussed in detail with examples in this article below.
Major Concepts of Function Argument Passing in Python
Arguments are always passed to functions by reference in Python. The caller and the function code blocks share the same object or variable. When we change the value of a function argument inside the function code block scope, the value of that variable also changes inside the caller code block scope regardless of the name of the argument or variable. This concept behaves differently for both mutable and immutable arguments in Python.
In Python, integer
, float
, string
and tuple
are immutable objects. list
, dict
and set
fall in the mutable object category. This means the value of integer
, float
, string
or tuple
is not changed in the calling block if their value is changed inside the function or method block but the value of list
, dict
or set
object is changed. Consider the mutable and immutable as states of the function arguments in Python. Let’s discuss it in detail with examples in the following section.
Python Immutable Function Arguments
Python immutable objects, such as numbers
, tuple
and strings
, are also passed by reference like mutable objects, such as list
, set
and dict.
Due to state of immutable (unchangeable) objects if an integer or string value is changed inside the function block then it much behaves like an object copying. A local new duplicate copy of the caller object inside the function block scope is created and manipulated. The caller object will remain unchanged. Therefore, caller block will not notice any changes made inside the function block scope to the immutable object. Let’s take a look at the following example.
Python Immutable Function Argument – Example and Explanation
def foo1(a): # function block a += 1 print('id of a:', id(a)) # id of y and a are same return a # main or caller block x = 10 y = foo1(x) # value of x is unchanged print('x:', x) # value of y is the return value of the function foo1 # after adding 1 to argument 'a' which is actual variable 'x' print('y:', y) print('id of x:', id(x)) # id of x print('id of y:', id(y)) # id of y, different from x
Result:
id of a: 1456621360
x: 10
y: 11
id of x: 1456621344
id of y: 1456621360
Explanation:
- Original object integer
x
is immutable (unchangeable). A new local duplicate copya
of the integer objectx
is created and used inside the functionfoo1()
because integers are immutable objects and can’t be changed in placed. The caller main block where variablex
is created has no effect on the value of the variablex.
- The value of variable
y
is the value of variablea
returned from functionfoo1()
after adding1
. - Variable
x
andy
are different as you can see their id values are different. - Variable
y
anda
are same as you can see their id values are same. Both point to same integer object.
Python Mutable Function Arguments
Python mutable objects like dict
and list
are also passed by reference. If the value of a mutable object is changed inside the function block scope then its value is also changed inside the caller or main block scope regardless of the name of the argument. Let’s take a look at the following diagram and code example with the explanation at the end when we assign list1 = list2
.
Python Mutable Function Argument – Example and Explanation
def foo2(func_list): # function block func_list.append(30) # append an element def foo3(func_list): # function block del func_list[1] # delete 2nd element def foo4(func_list): # function block func_list[0] = 100 # change value of 1st element # main or caller block list1 = [10, 20] list2 = list1 # list1 and list2 point to same list object print('original list:', list1) print('list1 id:', id(list1)) print('value of list2:', list2) print('list2 id:', id(list2)) foo2(list1) print('\nafter foo2():', list1) print('list1 id:', id(list1)) print('value of list2:', list2) print('list2 id:', id(list2)) foo3(list1) print('\nafter foo3():', list1) print('list1 id:', id(list1)) print('value of list2:', list2) print('list2 id:', id(list2)) foo4(list1) print('\nafter foo4():', list1) print('list1 id:', id(list1)) print('value of list2:', list2) print('list2 id:', id(list2))
Result:
original list: [10, 20]
list1 id: 24710360
value of list2: [10, 20]
list2 id: 24710360
after foo2(): [10, 20, 30]
list1 id: 24710360
value of list2: [10, 20, 30]
list2 id: 24710360
after foo3(): [10, 30]
list1 id: 24710360
value of list2: [10, 30]
list2 id: 24710360
after foo4(): [100, 30]
list1 id: 24710360
value of list2: [100, 30]
list2 id: 24710360
Explanation:
- We have created a list object
list1
and assigned same object to a new variablelist2
. Now bothlist1
andlist2
points to the same memory where actual list object[10, 20]
is stored. - We passed the value
list1
variable into the function argumentfunc_list
. We appended, deleted and modified thelist1
object element in functionfoo2()
,foo3()
andfoo4()
through argumentfunc_list
. - As you have noticed that actual object
list1
is changed in the main block when we changed its value in the function block. - You should also notice that the value of
list2
variable also changes when the value oflist2
changes. As we have also read, this is because bothlist1
andlist2
variable points to samelist
object[10, 20]
. list1
object ID doesn’t change after every call to functionfoo2()
,foo3()
andfoo4().
This is becauselist1
is mutable and can be modified. Therefore, changinglist1
object modifies original object value and doesn’t create the new object.
Now, we discuss the most interesting and confusing part of Python function arguments which is default mutable function arguments. First, we discuss about default function arguments then mutable default function arguments.