Why Isn't Python Pass-By-Value or Pass-By-Reference?

Posted on 2017-03-28 in Python, Functional Programming

Something as trivial as passing input in the form of arguments to a function and getting the desired output has been prevalent since the existence of functional programming paradigm.

Some of the popular languages such as C/C++ evaluate functions on the basis of two strategies:

Python on the other hand, uses a strategy called Pass By Object Reference which means:

The reference to an object is passed by value.


Objects objects everywhere...

Everything is an object in Python. With that said, even a primitive operation like assignment is influenced by this very concept. Variables in Python are more formally called names and their values represents an object.

Consider the following snippet for better understanding:

>>> a = 10
>>>
>>> id(a)
140709057226688
>>>
>>> b = 'some_string'
>>>
>>> id(b)
140709057227008
>>>
>>> c = 10
>>>
>>> id(c)
140709057226688
>>>

Note: id() gives the memory location.


In the above snippet we can clearly see that names a and c refer to the same object 10 located in the same memory location, which basically implies, there is no new memory allocation for an object irrespective of multiple assignments within a particular scope.

Assignment in Python can be narrowed down to the following:

  • it is simply a binding of name to an object
  • names are just a reference to an object stored in the memory in a particular scope
  • there is nothing such as variables, instead called Names more formally in the Python world

Depending on the nature of objects, they can be classified into two types:

  • Mutable
  • Immutable

Now let's extend this concept into functions, try to understand how do they behave when names are passed in as arguments.


Passing mutable objects into functions

Mutable objects, are those whose value can be changed in place. Some of the mutable objects in Python are: list, values in a dictionary etc.

When reference to a mutable object is passed into a function, its value can be changed in place affecting the caller's scope until no rebinding is done. But when the reference is re-binded to a new object inside the function's scope, then the caller's scope has no information about it. Hence two different objects are created.

Consider couple of snippets for better clarity:

Snippet I

When the reference to an object remains the same in the function's scope.

>>> def appender(some_list):
...     some_list.append(100)
...     return some_list
...
>>>
>>> first_list = [10, 20, 30, 40, 50]
>>> print(id(first_list))
140271313438536
>>>
>>> second_list = appender(first_list)
>>> print(id(second_list))
140271313438536
>>>

In the above snippet list being a mutable object, it's memory location remains the same throughout even after modifying the object's value within the function's scope.


Snippet II

When the reference to a mutable object is re-binded in the function's scope.

>>> def rebind(some_list):
...     some_list = [100, 90, 80, 70]
...     return some_list
...
>>> first_list = [10, 20, 30, 40]
>>> print(id(first_list))
140133109036872
>>>
>>> second_list = rebind(first_list)
>>> print(id(second_list))
140133109036296
>>>
>>> print(first_list, second_list)
[10, 20, 30, 40] [100, 90, 80, 70]
>>>

In the above snippet the reference is re-binded to a new object. Hence there is a new object in memory at a different location without affecting the object in the outer scope.


Passing immutable objects into functions

Unlike mutable object, values of immutable objects can't be changed in place, until they are re-binded. Hence to make any changes to the value of an object reference, a new assignment is done. Some of the immutable objects in Python are: int, string, tuples etc.

Consider couple of snippets for better clarity:

Snippet I

>>> def adder(num):
...     return num + 10
...
>>>
>>> first_num = 20
>>> print(id(first_num))
140233839986944
>>>
>>> second_num = adder(first_num)
>>>
>>> second_num
30
>>>
>>> print(id(second_num))
140233839987264
>>>

In the above snippet, we can clearly see that num being an immutable object never gets changed in place.

Snippet II

>>> def adder(num):
...     num = num + 10
...     return num
...
>>>
>>> first_num = 20
>>> print(id(first_num))
140233839986944
>>>
>>> second_num = adder(first_num)
>>>
>>> second_num
30
>>>
>>> print(id(second_num))
140233839987264
>>>

In the above snippet, it is pretty evident that in order to change an immutable object a new assignment operation has to be done. And this new object is only restricted within the function's local scope and keeps the global scope unaffected.


Conclusion

Arguments to functions in Python is simply a reference to an object passed by value. Depending on the object being mutable or immutable the operations on that object may or may not require for a new assignment within the function's scope. Under the hood, everything is an object and Python's Object Data Model forms the basis for the same.

Would love to hear suggestions and feedback.

Cheers!