Functions in python; Organize code into blocks

Introduction

functions in python:

A function is a very important concept in programming languages and python programming language is no exception. It is used to organize code into blocks that we can reuse later. It makes the code more readable and modular. Moreover, it saves time when designing and running the code since it helps to keep the code clean and unrepeated. We just need to call a function whenever a similar task needs to be executed.

Python provides built-in functions like print(), range(), len(), sum(), set(), etc. but we can also create/write our own functions. These functions are called user-defined functions.

Defining functions in python

The keyword def introduces a function definition. It must be followed by the function name and the parenthesized list of formal parameters. The statements that form the body of the function start at the next line and must be indented.

Syntax:

def my_first_function():
    '''
    This is my first function. It prints a string.
    '''
    print("Hello! this is my first function")

# Calling a function
my_first_function()

Output:

Hello! this is my first function

Note: Function names should be lowercase (example: my_first_function), with words separated by underscores as necessary to improve readability (source: PEP 8 — Style Guide for Python Code).

Function call

Once the function is defined and structured correctly, we have to call the function to execute its statement otherwise your function does nothing. For example, my_first_function() in the above program is a function call. A function can be even called multiple times in a program.

Example:

def my_first_function():
    '''
    This is my first function. It prints a string.
    '''
    print("Hello! this is my first function")

# Calling a function 3 times
my_first_function()
my_first_function()
my_first_function()

Output:

Kello! this is my first function
Hello! this is my first function
Hello! this is my first function
More on defining functions in python

In the above example, we defined a function with empty parameters which do not take any arguments. Now, we will look into more on defining functions using arguments and parameters.

Example (defining functions in python using arguments and parameters):

def my_first_function(a, b): # a and b is a functions parameter
    sum = a + b
    print("sum of a and b = ", sum)
my_first_function(10, 5) # 10 and 5 is an argument passed to a function parameter

Output:

sum of a and b =  15

Parameters: A parameter is the variable listed inside the parentheses in the function definition. Therefore, a and b are called function parameters. They are variables local to the function.

Arguments: When we call the function, the value passed (10 & 5) inside the parenthesis is called the function’s arguments. The 10 and 5 will be passed to a and b in the function parameter respectively. And later it was passed to the function statement.

Arguments in functions:

When we are calling a function with an argument, a function must be called with the correct number of arguments as per the number of parameters assigned in the function definition. Meaning, if your function expects 2 arguments, you have to call the function with 2 arguments, not more, and not less. Otherwise, it will give an error. Let’s look into different types of arguments we can pass to a function parameter:

1. Positional argument

The most common way of assigning arguments to parameters is via the order in which they are passed or as per their position.

Example:

def person(name, age):
    print(f"Name:", name)
    print(f"Age:", age)
person('Dawa', 25)

Output:

Name: Dawa
Age: 25
2. Default argument values

A positional argument can be made optional by specifying a default value for the corresponding parameter when defining the function. We can provide a default value to an argument by using the assignment operator (=).

Example:

def person(name, age=40): # Specified default value for variable age.
    print(f"Name:", name)
    print(f"Age:", age)
person('Dawa')

Output:

Name: Dawa
Age: 40
3. Keyword argument

Keyword arguments allow us to ignore the order in which we entered the parameters in the function definition or even to skip some of them when calling the function.

Example:

def person(name, age):
    print(f"Name:", name)
    print(f"Age:", age)
person(age = 25, name = 'Dawa')

Output:

Name: Dawa
Age: 25
4. Arbitrary arguments, *args

Arbitrary arguments are used when you don’t know how many parameters you’re going to need in a function (variable number of parameters).

Normally, these variadic arguments will be last in the list of formal parameters, because they scoop up all remaining input arguments that are passed to the function. Any formal parameters which occur after the *args parameter are keyword-only arguments, which means they can only be used as keywords rather than positional arguments.

Example:

def cal(a, *args):
    print(f"a:{a}")
    print(f"args:{args}")

    add = a+ sum(args)
    print(f"Sum of numbers: {add}")
cal(5, 60,2,190)

Output:

a:5
args:(60, 2, 190)
Sum of numbers: 257
5. Arbitrary keyword arguments, **kwargs

Used when you have a variable number of keyword arguments. kwargs stands for keyword arguments and it built a dictionary of key: value pairs.

Example:

def person(**kwargs):
    print(f"kwargs:{kwargs}")
    if 'name' in kwargs:
        print(f"Your name is {kwargs['name']}")
person(name = 'Dawa', age = 25, Location = 'Thimphu')

Output:

kwargs:{'name': 'Dawa', 'age': 25, 'Location': 'Thimphu'}
Your name is Dawa
Return and Pass statement in a function
1. Return statement

The return statement returns with a value from a function instead of printing it out. It allows us to assign the output of the new function to a new variable and it is the last statement to be executed inside the function, meaning the return statement exits the function.

Example:

def sum(a, b):
    sum = a + b
    print("Before return statement")
    return sum
    # It won't be executed because the return statement is the last statement to be executed inside the function
    print("after return statement") 
s = sum(10,30)
print(f"sum of a and b is {s}")

Output:

Before return statement
sum of a and b is 40

We can return more than one value and in that case, it returns as a tuple.

Example:

def sum_and_product(a, b):
    return a+b, a*b
s,p = sum_and_product(5, 10)
print(f"sum is {s} and product is {p}")

Output:

sum is 15 and product is 50

If a function does not return explicitly, it returns None implicitly: return without an expression argument returns None. Falling off the end of a function also returns None.

Example:

def f1(a = None):
    if a:
        print(f"a is {a}")
    else:
        print(f"Function called without argument")
        print(f"a is ", a)
f1(10) # Function called by passing 10 as an argument to varibale a in the parameter
f1()   # Function called without argument

Output:

a is 10
Function called without argument
a is  None
2. Pass statement

Pass is a null operation — when it is executed, nothing happens. It is useful as a placeholder when a statement is required syntactically, but no code needs to be executed.

It is used as a placeholder for a function or conditional body when we are working on new code, allowing us to keep thinking at a more abstract level.

Example:

def f1():
    pass 
f1()

# No output but no error as well
Scopes and Namespaces

A namespace is a container (table) that contains the names of variables, functions, methods, and classes we defined or used in our program. It controls all the names in our program and it will also allow us to reuse them.

A name or variable can exist only in a specific part of our code. The portion of code where the name exists is called the scope of that variable and the binding between the variable and the object or value is stored in a namespace.

In Python there are 3 types of namespaces or scopes:

1. The built-in namespace: it contains Python built-in functions and is available across all files or modules. A namespace containing all the built-in names is created when we start the python interpreter and exists as long as we don’t exist.

Example such as print(), len(), range(), etc is the built-in names that will be stored in the built-in namespace.

2. The global(module) namespace: it contains all variables, functions, and classes we define in our scripts. They are available inside a single file or a module.

3. The local namespace: it contains names defined inside our own functions. They are available only inside the function in which they are defined.

Note: Scopes are nested – the local scope is nested inside the global scope, which is nested inside the built-in scope.

Variables that are created outside of a function are known as global variables. It can be used everywhere in a program, inside as well as outside of the function.

Example:

num = 67 # Global variable
def local_var():
    age = 35 # Local variable
    print(f"Value of local variable age is {age}")
    print(f"Value of global variable num accessed inside the function is {num}")
local_var()
print(f"Value of global variable num is {num}")

Output:

Value of local variable age is 35
Value of global variable num accessed inside the function is 67
Value of global variable num is 67

If you want to use the variable in a specific function or method, you declare it as a local variable. It can be accessed only inside a particular function, otherwise, local variable accessing from outside the function will give an error.

Example:

def local_var():
    age = 35 # local variable
    print(f"Value of local variable age is {age}") # 35
local_var()
print(f"Accessing local variable age from outside the function is {age}") # Error

Output:

Value of local variable age is 35
Traceback (most recent call last):
  File "c:/Users/Dawa Penjor/Desktop/python_prpject.py", line 5, in <module>
    print(f"Accessing local variable age from outside the function is {age}")
NameError: name 'age' is not defined

Using the keyword global, you can access the locally created variable from outside the function.

Example:

def local_var():
    global age
    age = 35 # local variable made global
    print(f"Value of local variable age is {age}") # 35
local_var()
print(f"Accessing local variable age from outside the function is {age}") # 35

Output:

Value of local variable age is 35
Accessing local variable age from outside the function is 35
Python is passed by object-reference

There are essentially three kinds of ‘function calls’:

  • Pass by value
  • Pass by reference
  • Pass by object reference

In Python, passing by reference or by value has to do with what are the actual objects you are passing. Therefore, Python is a PASS-BY-OBJECT or PASS-BY-OBJECT-REFERENCE programming language.

So, when you are passing an immutable object such as strings or tuples, the passing is done by a value. Because a new object will be created and will be destroyed when the function terminates. In pass-by-value, the function receives a copy of the argument objects passed to it by the caller, stored in a new location in memory.

Example:

def fun1(x):
    print(f"Id of x is {id(x)}")
    x = 10
    print(f"Value of x is {x}")
    print(f"Id of x is {id(x)}")
a = 20
fun1(a)
print(f"Value of a is {a}")
print(f"Id of a is {id(a)}")

Output:

Id of x is 140719434488064
Value of x is 10
Id of x is 140719434487744
Value of a is 20
Id of a is 140719434488064

Whereas, if you are passing a mutable object such as a list then you actually make this pass by reference. Because you are passing a pointer to the function and you can modify the object in the function body. In pass-by-reference, the function receives a reference to the argument objects passed to it by the caller, both pointing to the same memory location.

Example:

def fun1(lst):
    print(f"Id of lst is {id(lst)}")
    lst[1] = ['abc']
    print(f"Value of list is {lst}")
    print(f"Id of lst is {id(lst)}")
lst1 = [12,45,'good']
fun1(lst1)
print(f"Value of lst1 is {lst1}")
print(f"Id of lst1 is {id(lst1)}")

Output:

Id of lst is 1903616922048
Value of list is [12, ['abc'], 'good']
Id of lst is 1903616922048
Value of lst1 is [12, ['abc'], 'good']
Id of lst1 is 1903616922048
Documentation strings

The first statement of the function body can optionally be a string literal; this string literal is the function’s documentation string or docstring. It’s good practice to include docstrings in the code that you write. Docstrings are not necessary for non-public methods, but you should have a comment that describes what the method does. This comment should appear after the def line. A docstring explains to the public the modules, functions, classes, and methods.

Example:

def cal(a, b):
    '''
    This functions does simple arithmetic calculation
    '''
    sum = a + b
    product = a * b
    div = a / b
    sub = a - b
    return sum, product, div, sub
s, p, d, s = cal(20, 5)
print(f"Sum: {s}, Product:{p}, Division:{d}, Subtraction:{s}")
print(cal.__doc__) #will print function’s documentation string

Output:

Sum: 15, Product:100, Division:4.0, Subtraction:15

    This functions does simple arithmetic calculation
Lambda Expression (anonymous function)

Lambda expressions are another way to create functions. They are called anonymous functions because they don’t have a name like a normal function. The lambdas are a single line of logical code created with the lambda keyword where we cannot do assignments inside the lambdas expressions. However, it can be assigned to variables or passed as arguments to another function.

Note: All rules we’ve seen in the function’s arguments apply to lambdas too.

Syntax:

lambda[parameter list]: expression

Example 1:

# Defining function
def mul(a, b):
    return a*b
m = mul(10,5)
print(f"10 x 5 by defining function normally: {m}")

# Using lambda function
s = lambda x,y: x*y
print(f"10 x 5 by lambda function:{s(10,5)}")

Output:

10 x 5 by defining function normally: 50
10 x 5 by lambda function:50

Example 2: (lambda passed as arguments to another function)

def my_funciton(x, fun):
    return fun(x)
y = my_funciton(5, lambda x: x**2)
print(y)

# Output
25