14  Functions

In programming when a sub-set of code is to be execute in one go, we have an option to give a name to that sub-set and that name is now a function. As an analogy consider the steps in making tea.

Make Tea
- Boil water
- Add tea
- Add sugar
- Add milk

Make Tea with options
- Boil water
- Add tea
- Add sugar
- If black tea skip this step
  else Add milk

The above steps need to be done every time we make tea. Also, we can create a new sub-set with some customization — “Make Tea with options”. So, whenever we say “make tea” it is implied that the step mentioned above would be performed and we don’t need to mention the steps again; just make tea is enough. Similarly, in programming, we can assign a keyword to a code block such that every time that keyword is called all the code within that block gets executed. Continuing with our analogy, we now have two “functions” – “Make Tea” and “Make Tea with options”.

So far we have used various in-built Python functions. Now let’s look at how we can define our own functions to do a specific task. To define a function, def keyword is used followed by a function name and parenthesis. The def statement ends with a colon and the statement within the function are indented (a syntax similar to for and if statements). A function can be initialized with or without arguments. To call a function, specify the function name along with the parenthesis. Below is a “Hello world!” function.

def newFunction():
    print("Hello World!")
newFunction()
Hello World!

The argument(s) that a function takes goes inside the parenthesis when defining the function. An argument can be positional i.e. it must be specified in a particular sequence or it can be a keyword argument (these can be specified in any order). An important point to note about functions is that the variables within a function have a local scope i.e. a variable declared within a function cannot be accessed outside the function. So, let’s say we have a function that does some calculation and we would like to get the result of that calculation. In such cases, we need to return the variable(s) using the return keyword. When calling the function these returned values are assigned to specified variables. Below is an example of a function with two positional arguments. Notice that when calling this function the order of argument is important.

14.1 Arguments

def calcPower(a,b):
    c = a**b
    return c
output_1 = calcPower(3,4)
print(output_1)
output_2 = calPower(4,3)
print(output_2)
81
64

14.2 Keyword arguments

We can have the same function with keyword arguments such that now the arguments can be specified in any order. All keyword arguments need to have a default value. If a value for a keyword argument is not specified while calling the function, the default value of the keyword is used.

def calcPower(number=1,power=1):
    c = number**power
    return c
output_1 = calcPower(power=3,number=4)
print(output_1)
64

It is important to note here that keyword arguments can be specified without keywords as well when calling a function. In this case, the order of arguments passed would be matched to the order of the keywords in the function definition.

calcPower(2,3)
8

14.3 Variable number of arguments

A function can have both positional and keyword arguments and in such case, the keyword arguments must be specified after positional arguments.

It is also possible to have an arbitrary number of positional and keyword arguments. The *args argument maintains a tuple named args that stores all the arguments passed when a function is called.

def sumNums(*args):
    '''This function would add any set of numbers.
    It has been defined using *args.'''
    c = 0
    for num in args:
        c = c+num
    return c
print(sumNums(4,5,6))
15

The tripple quoted string in the above function not only acts a comment but is also available as help for this function accessible via help function.

help(sumNums)
Help on function sumNums in module __main__:

sumNums(*args)
    This function would add any set of numbers.
    It has been defined using *args.

Quiz: Write a function to calculate (a + b)2.

Show answer
def squareSum(a,b):
    c = a**2 + b**2 + 2*a*b
    return c
num1 = 4
num2 = 5
print(f"The square of sum of {num1} and {num2} is {squareSum(num1,num2)}.")

14.4 Variable number of keyword arguments

The **kwargs argument is used to have an arbitrary number of keyword arguments. Note the two asterisks. In this case, a kwargs dictionary is maintained which as the keyword and its value as key-value pairs of the dictionary.

def func1(**kwargs):
    all_keywords = kwargs.keys()
    all_values = kwargs.values()
    return all_keywords, all_values
k1,v1 = func1(name="Sam", age=20)
print(k1)
print(v1)
dict_keys(['name', 'age'])
dict_values(['Sam', 20])

14.5 Reusing functions

Function is a python file can be accessed in another python code. E.g., we have a file fileA.py that has function funcInFileA. Now to call this function from FileB.py we need to import this function — from fileA import funcInFileA.

Recommendation

To practice function import it is advisable to do it on a console rather than inside notebook.

# fileA.py
def funcInFileA():
    print("Inside File A")
#fileB.py
from fileA import funcInFileA
funcInFileA()
Inside File A

Save the above two files in the same folder and then execute fileB.py. If we execute file fileA.py then there would be no output since we are not calling the function funcInFileA in that file. In case we do call funcInFileA in fileA.py then this function would be called twice upon calling from fileB.py.

If you would like to have an option to call funcInFileA from both the files and prevent calling it twice when importing this function then you need to use a special variable called __name__ (note the two underscores at the beginning and the end). This variable stores the name of the module which is being executed. Each python file or a module has a __name__ variable associated with it. When a python file is executed this variable is assigned a value __main__.

print(__name__)
__main__

Here the value is __main__ because we are executing the code directly. When we import a function then the value of the __name__ variable is set to the filename in which we have the imported function. So the recommended way to call funcInFileA would be to first check the value of __name__ followed by conditional calling. At the end of fileA.py, we can the following code.

#Add this to fileA.py
if __name__ == "__main__":
    funcInFileA()

Now, the function is called when we execute either of the files. Upon executing fileB.py, since the function is imported, the __name__ variable for fileA would not be equal to __main__ and hence would not be called because the if condition there would be false.