Functions
What is a Function?
Function is a set of Python codes that performs a task or returns a result.
So far, you have learnt how to use some of the builtin function in Python, such as print(), len(), range(), min(), max() etc.
You can write lines of codes to perform a task or return results. But the lines of code gets larger from few lines to 100s or 1000s of lines of code. It is recommended to be break down the code to smaller chunks. This is helpful to make your code more maintainable and readable as well as avoid repetition by reusing chunks of codes. These reusable chunks are called Functions.
What are the Types of Functions?
There are generally two types of functions:
Perform a task
Return a value(s) or an expression(s)
What is a Function Syntax?
In Python, the building block of a function is as follow:
Function starts with
defkeywordA uniquely identified name for the function
Parameters to pass the value(s) of the function into the function. This is optional.
A way to define where the function header finishes:
colon :Multi-line string to document what the function input and output is or performs. This is also optional but it is recommended.
Python statement(s) that performs a task
If you want the function to return an output, you can use
returnstatement. It is also optional.
def function_name(inputs):
"""
docstrings
"""
# Python statements
return outputsWhat is return Statement?
return Statement?The return statement is used to exit a function and return an output. The output can be assigned to a variable with assignment operator. The return statement is:
return output(s)The return statement evaluates the function and returns the output. It will return a None object where there is either no expression to return or no return statement is present.
An Example of a Function
An example of a function that takes two integers and return the sum of the values. The function is created below:
def take_sum(value_1_int, value_2_int):
"""
inputs:
output:
"""
sum_of_values = value_1_int + value_2_int
return sum_of_valuesSemantics: Arguments vs Parameters
In the context of take_sum() function, value_1_int and value_2_int are called parameters of take_sum() function. value_1_int and value_2_int are variables locally connected to the take_sum() function.
You can call the function name followed by a parentheses () with the arguments in between.
value_1_int = 2
value_2_int = 10
take_sum(value_1_int, value_2_int)
#output
12Now, value_1_int and value_2_int are called the arguments of take_sum() and you also passed value_1_int and value_2_int by reference. i.e. memory addresses of the value_1_int and value_2_int are passed to the function.
A function can be created without parameters.
def greeting():
print('Good day')
print('Stay safe and healthy')greeting()
# output
Good day
Stay safe and healthyWhat is Positional Arguments?
Positional Arguments is the order in which parameters are passed into a function. It is the most common way of assigning arguments to parameters.
take_sum(2, 10) #--> value_1_int = 2, value_2_int = 10
take_sum(10, 2) #--> value_1_int = 10, value_2_int = 2
take_sum(value_1_int = 10, value_2_int = 2)What is a Default Values in a Function?
A positional arguments can be made optional by specifying a default value for the corresponding parameter. Let's take the take_sum() function as an example.
def take_sum(value_1_int, value_2_int=10):
sum_of_values = value_1_int + value_2_int
return sum_of_values
take_sum(2)
# output: 12x = 2
y = 100 # it was 10
take_sum(x, y)
take_sum(2, 100)
# output: 102The default value always comes as a last parameter in the function or any parameter that comes after it has to be assigned with default value. Let's modify take_sum() function to have three parameters.
def take_sum(value_1_int, value_2_int=10, value_3_int):
sum_of_values = value_1_int + value_2_int + value_3_int
return sum_of_values
# output
# File "<ipython-input-3-4a4bb3b6c326>", line 1
# def take_sum(value_1_int, value_2_int=10, value_3_int):
# ^
# SyntaxError: non-default argument follows default argumentdef take_sum(value_1_int, value_2_int=10, value_3_int=20):
sum_of_values = value_1_int + value_2_int + value_3_int
return sum_of_values
# or
def take_sum(value_1_int, value_3_int, value_2_int=10):
sum_of_values = value_1_int + value_2_int + value_3_int
return sum_of_valuesBut what if you want to specify the 1st and 3rd arguments, but omit the 2nd argument?
i.e. you want to specify values for value_1_int and value_3_int, but let value_2_int take on its default value.
take_sum(value_1_int=2, value_3_int=30)
take_sum(2, 30)
# or
take_sum(2, value_3_int=30)Keyword arguments
Optionally, positional arguments can be specified by using the parameter name regardless of the parameters with or without default values.
take_sum(2, 10, 20)
# or
take_sum(2, 10, value_3_int=20)
# or
take_sum(value_1_int=2, value_2_int=10, value_3_int=20)
# or
take_sum(value_3_int=20, value_1_int=2, value_2_int=10)When you use a named argument, it is required to use named arguments for all arguments after it.
take_sum(value_3_int=20, 10, 2)
# or
take_sum(2, value_3_int=20, 10)
# output
# File "<ipython-input-8-86d09888c0c5>", line 6
# take_sum(value_3_int=20, 10, 2)
# ^
#SyntaxError: positional argument follows keyword argumentYou can also not call the named arguments when you are calling your function
take_sum(2) # value_1_int=2, value_2_int=10, value_3_int=20
take_sum(2, 100) # value_1_int=2, value_2_int=100, value_3_int=20
take_sum(2, 100, 200) # value_1_int=2, value_2_int=100, value_3_int=200What is *args?
*args?Remember the iterable unpacking
x, y, z = 1, 2, 3Similar things happens when positional arugments are passed to a function
take_sum(value_1_int, value_2_int, value_3_int)
take_sum(2, 4, 6)There is a handy trick that can be used during tuple unpacking when you have imbalance on right and left of the assignment operator. You can use a * operator to create a tuple/list from several values.
x, y, z = 1, 2, 3, 4#output
ValueError Traceback (most recent call last)
<ipython-input-9-751b9158939d> in <module>()
----> 1 x, y, z = (1, 2, 3, 4)
ValueError: too many values to unpack (expected 3)The above code gives an error. But we can overcome that with * operator. The first and second will be assigned to x and y respectively. The third and fourth will be a list and assigned to z
x, y, *z = 1, 2, 3, 4
print(x)
print(y)
print(z)
print(type(z))#print(x)
1
#print(y)
2
#print(z)
[3, 4]
#print(type(z))
<class 'list'>Let's define the function again, notice the start at the start of third value.
def take_sum(value_1_int, value_2_int, *value_3_int):
print(value_1_int)
print(value_2_int)
print(value_3_int)
take_sum(1, 2, 'string1', 'string2')
# value_1_int = 1
# value_2_int = 2
# value_3_int = ('string1', 'string2') # a tuple (a minor difference)The parameter name value_3_int is arbitrary. You can choose any name you would like, but it is customary to name it *args. So your function can be updated as below.
def take_sum(value_1_int, value_2_int, *args):
print(value_1_int)
print(value_2_int)
print(args)*args will not allow anymore positional arguments. But there is a way around it.
def take_sum(value_1_int, value_2_int, *args, value_3_int):
print(value_1_int)
print(value_2_int)
print(args)
print(value_3_int)This will not work
take_sum(2, 10, 30, 50, 20)This will work
take_sum(2, 10, 30, 50, value_3_int = 20)Unpacking arguments
You can pass an iterable argument into a function with the * operator.
def take_sum(value_1_int, value_2_int, value_3_int):
sum_of_values = value_1_int + value_2_int + value_3_int
return sum_of_values
values = [2, 10, 20]
take_sum(*values)
# or
take_sum(2, 10, 20)
# output
#32What is Keyword Arguments?
The positional parameters can, optionally, be passed as named (keyword) arguments. For example, if we define a function with three positional arguments:
def get_sum(int_1, int_2, int_3):
total = int_1 + int_2 + int_3
return totalWe can call the function get_sum() and pass the arguments. The named arguments is optional and it is up to you.
# you can call the function
get_sum(2, 3, 4)
#or
get_sum(int_1=2, int_3=4, int_2=3)You can also rewrite the function above to make more intuitive and practical. Using *args, you can pass many values at the same time.
def get_sum(*args):
total=0
for value in args:
total += value
return total Mandatory Keyword Arguments
Sometimes, you need to make the keyword arguments mandatory and force the user to call the named arguments. You can do so by exhausting all the positional arguments, and specify another additional parameter or parameters in the function definition.
def get_sum(int_1, int_2, *args, int_last):
total = int_1 + int_2 + int_last
return total, argsThe presence of *args exhausts all positional arguments and int_3 MUST be passed as a named argument.
In the function get_sum(), you will need to pass two positional arguments, one or more additional arguments (optional), and a mandatory keyword argument which goes into int_3. The int_3 argument can only be passed to the function using a named (keyword) argument.
This will not work
get_sum(2, 3, 22, 33, 44, 4)TypeError Traceback (most recent call last)
<ipython-input-9-91cc736c70b0> in <module>()
----> 1 get_sum(2, 3, 22, 33, 44, 4)
TypeError: get_sum() missing 1 required keyword-only argument: 'int_3'But using named argument for int_3, it works.
get_sum(2, 3, 22, 33, 44, int_3=4)
#or
get_sum(1, 2,int_3=5)You can even define a function that has only optional positional arguments and mandatory keyword arguments, i.e. you can force no positional arguments at all.
def get_sum(*args, max):
total=0
for value in args:
total += value
total += max
return totalThe function get_sum() can be rewritten to provide no positional arguments at all.
def get_sum(*, max):
total=0
for value in args:
total += value
total += max
return totalNow, you have only * without specifying the name (usually args). The * indicates the end of positional arguments. You tell your function that you are not looking for positional arguments, effectively, there is no positional arguments.
if you call the function and provide positional arguments, Python raises an Exception error.
get_sum(2, 3, 22, 33, 44, max=4)TypeError Traceback (most recent call last)
<ipython-input-14-a7f9508d7a76> in <module>()
----> 1 get_sum(2, 3, 22, 33, 44, max=4)
TypeError: get_sum() takes 0 positional arguments but 5 positional arguments (and 1 keyword-only argument) were givenNote: The aims is to showcase no positional arguments and the get_sum() function has to be fixed.
In nutshell: *args vs *
# Function 1
def func(x, y=1, *args, z, factor=True):
# statements
# Function 2
def func(x, y=1, *, z, factor=True):
# statementsx: mandatory positional argument (may be specified using a named argument)
y: optional positional argument (may be specified positionally, as a named argument, or not at all), defaults to 1
args vs *
args: catch-all for any (optional) additional positional arguments
*: no additional positional arguments allowed
z: mandatory keyword argument
factor: optional keyword argument, defaults to True
What is **kwargs?
You have a clear understanding what*args is by now. **kwargs works very similar *args, but instead of accepting positional arguments (*args returns a tuple) and accepts named arguments.
**kwargs is used to accept a variable amount of remaining keyword arguments. It returns a dictionary.
**kwargs can be specified even if the positional arguments have NOT been exhausted. This is opposite to *args.
Let's define a function:
def new_func(*, int_1, **kwargs):
print(int_1)
print(kwargs)
new_func(int_1=1, x=2, y=4)#print(int_1)
1
#print(kwargs)
{'x': 2, 'y': 4}You can also use it by passing only the named argument int_1
new_func(int_1=1)This will print only the mandatory positional argument and an empty dictionary.
# Output
1
{}You can also create a function by just passing a **kwargs. For example:
def new_func(**kwargs):
print(kwargs)
new_func(x=2, y=4, z=6){'x': 2, 'y': 4, 'z': 6}You can even create a function with both *args and **kwargs.
def new_func(*args, **kwargs):
print(args)
print(kwargs)
new_func(11, 22, 33, x=2, y=4, z=6)#print(args)
(11, 22, 33)
#print(kwargs)
{'x': 2, 'y': 4, 'z': 6}Can we mix args and kwargs as input to a function?
You can even call the function without providing any arguments. This result in returning an empty tuple () and an empty dictionary {}.
new_func()
# Output
()
{}You can check the Jupyter Notebook in Colab
Last updated
Was this helpful?