In Python, objects and data can be passed in several ways:
Pass by reference: When an object is passed as an argument to a function, a reference to the object is passed, not a copy of the object. Any changes made to the object within the function will be reflected outside the function as well.
def modify_list(my_list):
my_list.append(4)
print("Inside function:", my_list)
my_list = [1, 2, 3]
print("Before function call:", my_list)
modify_list(my_list)
print("After function call:", my_list)
The output of this code will be:
Before function call: [1, 2, 3]
Inside function: [1, 2, 3, 4]
After function call: [1, 2, 3, 4]
As you can see, the append()
method is called inside the function, which adds the number 4 to the list. Even though the list is passed as an argument to the function, the change made inside the function is reflected outside the function as well, because the list is passed by reference. Let's now examine another way we pass objects.
Pass by value: In python, all variables are references, whether it is a list, a dictionary, a string, a boolean or even an integer. Python does not have a direct pass by value mechanism, but it is possible to achieve pass by value behavior by passing a copy of the object to the function. The copy module can be used to create a shallow copy of the object, and the deepcopy module can be used to create a deep copy of the object. Here is an example of passing a list as an argument to a function:
def modify_list(my_list):
my_list[0] = 'Changed'
print("Inside function:", my_list)
my_list = ['original', 'values']
print("Before function call:", my_list)
modify_list(my_list)
print("After function call:", my_list)
In this example, the modify_list
function takes a list as an argument and modifies the first element of the list. Then, it prints the list both before and after the function call.
The output of this code will be:
Before function call: ['original', 'values']
Inside function: ['Changed', 'values']
After function call: ['Changed', 'values']
As you can see, the first element of the list is changed inside the function, and this change is reflected outside the function as well because the list is passed by reference.
Pass by object: Python uses a concept called "call-by-object", which is similar to call-by-reference. When an object is passed as an argument to a function, a reference to the object is passed, and any changes made to the object within the function will be reflected outside the function. However, if the object is immutable, any changes made to the object will result in a new object, and the original object will remain unchanged.
Here's an example of how we can create a new reference to an object within a function and make changes to it, without affecting the original object:
def modify_list(my_list):
my_list = my_list + [4]
print("Inside function:", my_list)
my_list = [1, 2, 3]
print("Before function call:", my_list)
modify_list(my_list)
print("After function call:", my_list)
In this example, the modify_list
function takes a list as an argument, but it creates a new reference to the list and assigns it to the variable my_list
. Then, it modifies the new list by appending the number 4 to it. Finally, it prints the original list both before and after the function call.
The output of this code will be:
Before function call: [1, 2, 3]
Inside function: [1, 2, 3, 4]
After function call: [1, 2, 3]
As you can see, the original list is not modified, because the function created a new reference to the list and made changes to that reference.
This example is not exactly pass by object, but it shows how we can make changes to a new reference of an object and not affect the original one.
Default arguments: When a default value is specified for an argument in a function definition, this value is used as the default value for that argument when the function is called. In Python, default arguments allow you to specify default values for function parameters. If a value for a parameter is not provided when the function is called, the default value will be used. Here is an example of a function with default arguments:
def greet(name, greeting = "Hello"):
print(greeting + ", " + name + "!")
greet("Akiva") # "Hello, Akiva!"
greet("Sarah", "Shalom") # "Shalom, Sarah!"
In this example, the function greet
takes two parameters: name
and greeting
. The greeting
parameter has a default value of "Hello". When the function is called with only one argument, the default value is used for the greeting. When the function is called with two arguments, the second argument overrides the default value.
It's important to note that Default arguments are evaluated only once at the time of function definition. It means that if the default argument is a mutable object, like a list or a dictionary, and you modify it inside the function, the change will be reflected in the next call as well.
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [1, 2]
In this example, since the default argument is a list, when the list is modified inside the function, the change will be reflected in the next call as well.
To avoid this, you can use None as the default value and create a new object inside the function if the value is None.
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item(1)) # [1]
print(add_item(2)) # [2]
In this way, a new object is created every time the function is called, avoiding unexpected behavior.
Variable-length arguments: Python supports variable-length arguments using the and * operators. The operator is used to pass a variable number of non-keyword arguments to a function, and the * operator is used to pass a variable number of keyword arguments to a function.
In Python, variable-length arguments allow a function to take an unspecified number of arguments.
There are two types of variable-length arguments in Python:
*args
: used to pass a variable number of non-keyword arguments to a function.**kwargs
: used to pass a variable number of keyword arguments to a function.
Here's an example of a function that uses *args
to take a variable number of non-keyword arguments:
def print_args(*args):
for arg in args:
print(arg)
print_args(1, 2, 3, "hello", "world")
In this example, the function print_args
takes a variable number of arguments and prints each one. When the function is called, any number of arguments can be passed, and they will be collected into a tuple called args
.
Here's an example of a function that uses **kwargs
to take a variable number of keyword arguments:
def print_kwargs(**kwargs):
for key, value in kwargs.items():
print(key + ": " + value)
print_kwargs(name="John", age=25, city="New York")
In this example, the function print_kwargs
takes a variable number of keyword arguments and prints each one in the format "key: value". When the function is called, any number of keyword arguments can be passed, and they will be collected into a dictionary called kwargs
.
It's also possible to use both *args
and **kwargs
in the same function, you can use them in any order but typically *args
is first and then **kwargs
def print_args_kwargs(*args, **kwargs):
for arg in args:
print("arg: ", arg)
for key, value in kwargs.items():
print("kwarg: ", key, value)
print_args_kwargs(1, 2, 3, name="John", age=25, city="New York")
In this example, the function print_args_kwargs
takes a variable number of arguments, both non-keyword and keyword, and prints each one. The non-keyword arguments are collected into a tuple called args
and the keyword arguments are collected into a dictionary called kwargs
.
It's important to note that the use of *
and **
is only a convention to indicate that the function accepts variable-length arguments, you can use any name you want.
This concludes our exploration of functions in python. How python functions works and some of their use cases. Go forth and practice, as well as share and subscribe.