The concept of a variable in Python is a little different from C, where it is a named memory location with a unique address. In Python, on the other hand, data objects are stored in memory, and a variable is just a label for its easy access. This is why Python is a dynamically typed language, where the type of variable need not be declared before assignment and data decides the type and not other way round (as in C/C++).
>>> x=100
>>> type(x)
<class 'int'>
Here, an integer object 100 is stored in memory, and for our convenience, a label (which we call as variable) x
is assigned to it. The same label assigned to a different object changes its type.
>>> x="Hello World"
>>> type(x)
<class 'str'>
Secondly, the Python interpreter assigns a unique identifier to each object. Python's built-in id() function returns this id, which is roughly equivalent to a memory address.
>>> x=100
>>> id(x)
140717823786336
Note that if x
assigned to another variable y
, both have same ids, which means both are referring to same object in memory.
>>> y=x
>>> id(x), id(y)
(140717823786336, 140717823786336)
As a result, actual and formal arguments involved in a function call have the same id value.
def myfunction(arg):
print ("value received {} has id {}".format(arg, id(arg)))
x=100
print ("value sent {} has id {}".format(x, id(x)))
myfunction(x)
value sent 100 has id 140717823786336
value received 100 has id 140717823786336
Hence, it can be inferred that in Python, a function is always called by passing a variable by reference. It means, if a function modifies data received from the calling environment, the modification should reflect in the original data. However, this is not always true.
If the actual argument variable represents an immutable object such as int, float, tuple, or a string, any modification inside function will result in creating a different object (which will be local to the called function) and not affect the original variable.
def myfunction(arg):
print ("value received {} has id {}".format(arg, id(arg)))
arg=arg+10
print ("value changed {} has id {}".format(arg, id(arg)))
x=100
print ("value sent {} has id {}".format(x, id(x)))
myfunction(x)
print ("value after function call {} has id {}".format(x, id(x)))
value sent 100 has id 140717823786336
value received 100 has id 140717823786336
value changed 110 has id 140717823786656
value after function call 100 has id 140717823786336
It can be seen that incrementing the received argument (it is an int object which is immutable) creates a new object (with a different id) and doesn't affect the original object referred to x
.
Instead, if a mutable object such as a list is passed to function, modification inside a function will be reflected after function call as in the following function.
def myfunction(arg):
print ("list received {} has id {}".format(arg, id(arg)))
arg.append(40)
print ("list changed {} has id {}".format(arg, id(arg)))
x=[10,20,30]
print ("list sent {} has id {}".format(x, id(x)))
myfunction(x)
print ("list after function call {} has id {}".format(x, id(x)))
list sent [10, 20, 30] has id 1864061271048
list received [10, 20, 30] has id 1864061271048
list changed [10, 20, 30, 40] has id 1864061271048
list after function call [10, 20, 30, 40] has id 1864061271048
In the end, we can conclude that even though any object is passed to a function by reference, a function's changes are reflected only in the case of mutable objects and not in immutable objects.