Monday, April 28, 2025

Functions

 Functions

Functions are developed as a part of programs to handle the following scenarios.
1. Modular programming: It is difficult to write the entire application/large programs in one file and by one programmer. Hence, the application's big/huge task is divided into smaller tasks, which are implemented as functions. They can all be written in separate files by different programmers and finally made into one program/application.
2. Repetitive use of a piece of code: The program/application may use a small piece of code repetitively. Such code segments are written separately as functions and called when necessary for the program. This improves the readability of the program and also saves memory. 
3. Library functions: Functions are developed by the language developer and provided to users as part of the library. These functions are used by program writers as and when needed for program development. For example, all math functions (sqrt, sin, cos) are available for users.
4. Sharing domain expertise: Domain experts produce library functions in their respective domains and make them available to the users as third-party packages.

Python program/script with functions

Function with no arguments: The function fn1 in the following program has no arguments. The program is useful to understand function definition and function call.

def fn1():                        # function definition starts here
    print("This is a function with no arguments")            # function definition ends here

fn1()                                # function call

Read the program with comments. The function definition starts with the keyword def, followed the name of the function (fn1) and its parameters within the brackets. In this function, there are no parameters. The function is called by the following line of code in the program.

fn1()

The function call is just the name of the function and the arguments within the brackets. In this program, there are no arguments to pass to the function.

Function with arguments: The function fn2() in the following program has two parameters, a and b. The function simply prints the values of a and b.

def fn2(a,b):
    print(a,b)

v1 = 100
v2 = 200
fn2(v1,v2)
fn2(300,400)

The fn2 is called with arguments v1 and v2 by the line of code

fn2(v1,v2)

The values of v1 and v2 are assigned to a and b, respectively, and they are printed when executed. The function fn2 is called again, but with the values directly as arguments (fn2(300,400)).

Function with default arguments: The function fn3() in the following program has default values for some of its parameters. When the function is called without these arguments, then the default values are used as arguments for the function.

def fn3(a, b=100, c=200):
    print(a,b,c)

v1 = 10
v2 = 20
v3 = 30
fn3(v1,v2,v3)                # function is called with all three arguments
fn3(v1)                     # function is called with only one argument; 2nd and 3rd uses  default values

The function call with only one argument v1 is assigned to the first parameter a, and the values of b and c are the default values 100 and 200, respectively. The function can also be called with two arguments as follows.

fn3(v1, v2)

The function call assigns v1 to a, v2 to b, but the value of c is the default value (200)

Different ways of passing arguments: The arguments to the functions may be passed in the following ways. For example, consider the range function.

range(start=0, stop, step=1)

The function has three parameters with names start, stop, and step. It may be called in the following ways

range(1,5,2)                     # passing arguments (coder knows the parameters and their order) range(start=1,stop=5,step=2)      # passing arguments along with names
range(stop=5, step=2, start=1)    # passing arguments with names but without following the order  

All three cases are syntactically correct. It depends on the programmer's awareness of the function parameters. In the third case, the coder is aware of the names of the parameters of the function but lacks the knowledge of their order. Also,

range(5)        # start and step are assigned with default values, the argument is assigned to stop
range(1,5)    # only the parameter step is assigned with the default value

But while using default values, the order of parameters must be followed.
 
A program with a function: Now, let us write a program to compute the factorial of a given number. The task of computing the factorial is written as a function.

def factorial(n):                    # function definition starts here
    answer = 1
    if n==0 or n==1:               # factorial of 0 and 1 is 1
        return answer
    for i in range(2, n+1):        # for example; 5! = 2*3*4*5; range function returns [2,3,4,5]
        answer = i*answer
    return  answer                # functions definition ends here

number = input("enter the number to find its factorial ")
number = int(number)
result = factorial(number)            # call the function factorial  with number as an argument
print("The factorial of the number is ", result)
   
In this Python script, 
name of the function: factorial,
parameter: n,
argument passed: number
function return value: answer

The program is self-explanatory for the reader with knowledge of functions, library functions (input() and int()).  The input() is used to read the number to find the factorial. But, the read number is in str type and hence typecasted to convert it into int type.

Scope of variables

The scope of variables in Python scripts may be local or global. Variables declared inside any of the functions are local to that function. They are not available anywhere outside that function. Whereas the variables declared outside the functions are global and available everywhere in the script for use, including functions. But, functions can't modify the global variables without declaring them as global inside the function using the keyword global. The following code script illustrates the scope of variables.

def fn1():   
    v1 = 100            # v1 is local to the function fn1()
    print(v, v1)

def fn2():                # second function starts here
    global v
    v2 = 200            # v2 is local to the function fn2()
    v = v2                # valid; function is entitled to modify v
    print(v, v2)    

v = 300                # v is global variable
print(v)
fn1()
fn2()                    # script ends here

The output of the script is 
300
300 100
200 200

The variables v1 and v2 are local to the functions fn1() and fn2(), respectively. Their scope and accessibility are restricted to the respective functions. On the other hand, the variable v is a global variable and is accessible by both functions fn1() and fn2(). But,  the fn1() can use the value of the global variable v without modifying it. To modify the value of any global variable inside any of the functions, it must be declared global inside that function as shown in fn2(). 

global v       

This statement inside the function fn2() makes the variable v available for modification.

  Exception handling / Error handling

Programs/applications are expected to run 24/7 if necessary. They should not get terminated by the accidental errors that occur while running and/or errors introduced by the users of the application. Python handles the errors that occur while running the program using the try-except statement. The syntax of the try-except statement is as follows.

try:
    statements that may result in an error, which terminate the program
except NameOfTheError:
    error handling code

The coder must identify the statements that may produce errors and place them in the try block.  It is also the coder's responsibility to identify the kind of error the try block code produces. If the code inside the try does not produce any error, then the except block is not touched. The program control will simply be transferred to the next statement in the program. If the try block code produces the error, then the except block will be executed. The program control will then be transferred to the next statement of the program to continue execution. After executing the except block code, the program control never goes back to the try block code.

List of some errors: 
ZeroDivisionError    -> happens when dividing a number by zero
ValueError                -> happens when passing the wrong value to a function
IndexError                -> happens when out of range index is used

The following Python script illustrates ZeroDivisionError.

numerator = 100
denominator = 0

try:
    value = numerator/denominator
except ZeroDivisionError:
    print("Divide by zero error occurred")

print("program runs 24/7")

The coder must know that the try block code produces ZeroDivisionError (not IndexError) and it must accompany the except keyword. If the except keyword accompanies IndexError, the error handling fails.

    Programs

Let us write a program to find out a binomial coefficient given n and r. 

binomial co-efficient =  n! / r! (n-r)!

To find the binomial coefficient, the program has to compute factorials three times. Hence, it is good to write a function to compute the factorial and call it three times with appropriate arguments.

# Program to compute binomial coefficients
# start with a function to compute factorial
def factorial(n):                    # function definition starts here
    answer = 1
    if n==0 or n==1:               # factorial of 0 and 1 is 1
        return answer
    for i in range(2, n+1):        # for example; 5! = 2*3*4*5; range function returns [2,3,4,5]
        answer = i*answer
    return  answer                # functions definition ends here

n = int(input("Enter the value of n = "))
r = int(input("Enter the value of r = "))

n_fact = factorial(n)
r_fact = factorial(r)
n_r_fact = factorial(n-r)

binomial_coefficient = n_fact / (r_fact*n_r_fact)
print("binomieal_coefficient = ", binomial_coefficient)

Enter the value of n = 6
Enter the value of r = 4
binomieal_coefficient =  15.

Users of the program should know that the value n must be greater than r. Othewise, if n and r are 4 and 6, the results are wrong. 

You can update this program to reject any such attempts (n<r) by the user. Also, make your factorial function to reject any negative input (n-r is negative n<r)