0% found this document useful (0 votes)
3 views32 pages

Python Functions

The document provides an overview of functions in Python, detailing their definition, types (user-defined and built-in), and advantages such as reusability and modularity. It explains how to create functions using the 'def' keyword, the importance of docstrings, and the various ways to call functions, including passing arguments and using return statements. Additionally, it covers concepts like parameters vs. arguments, call by reference, and different types of arguments including required, default, variable-length, and keyword arguments.

Uploaded by

anua62704
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views32 pages

Python Functions

The document provides an overview of functions in Python, detailing their definition, types (user-defined and built-in), and advantages such as reusability and modularity. It explains how to create functions using the 'def' keyword, the importance of docstrings, and the various ways to call functions, including passing arguments and using return statements. Additionally, it covers concepts like parameters vs. arguments, call by reference, and different types of arguments including required, default, variable-length, and keyword arguments.

Uploaded by

anua62704
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

1

Functions in Python

A function can be defined as the organized block of reusable code, which can be called
whenever required. Python allows us to divide a large program into the basic building blocks
known as functions. The function contains the set of programming statements. A function can
be called multiple times to provide reusability and modularity to the Python program.
The function helps to programmer to break the program into the smaller part. It
organizes the code very effectively and avoids the repetition of the code. As the program grows,
functions makes the program more organized. Python provides us various in-built(pre-defined)
functions like range() or print(). Although, the user(programmer) can create his own functions
whenever required, which can be called user-defined functions.

Types of functions.
 User-defined functions: user-defined functions are the function which are defined by
the user or programmer to perform a specific task.
 Built-in functions: built-in functions are the functions which are pre-defined in Python
language to perform a specific task.

Advantages of Functions: Using functions, we have the following advantages.


 We can avoid rewriting the same logic/code again and again in a program.
 We can call Python functions multiple times in a program and anywhere in a program.
 We can track a large Python program easily when it is divided into multiple functions.
 Reusability is the main achievement of Python functions.

Creating a Function:
Python provides the def keyword to define a user-defined function.

Syntax:
def function_name([parameters]):
‘’’ doc string if any‘’’
function_code
[return expression]

 The ‘def’ keyword along with the function_name is used to define the function.
 The identifier rules must follow the function_name.
 A function accepts the parameters (arguments), and they can be optional.
 The function block is start with the colon (:), and block of statements must be at the
same indentation.
 The return statement is used to return the value. In C, Java; function can have only
one return statement but in python a function can return multiple values at a time.
2

Docstring:
Python documentation string (or docstring) provide a convenient way of associating
documentation with Python modules, functions, classes, and methods. It is specified in source
code that is used, like a comment, to document a specific segment of code. Unlike conventional
source code comments, the docstring should describe what the function does.
 Declaring Docstring: The docstring is declared using triple single quotes or triple
double quotes just below the class, method, or function declaration. All functions
should have a docstring.
 Accessing Docstring: The docstring can be accessed using the __doc__ attribute of the
object or using the function_name or using the help() function.
Ex:
def my_function():
'''Demonstrates triple double quotes
docstrings and does nothing really.'''
return None

print(my_function.__doc__)
help(my_function)

Function Calling:
After the function is created, we can call it from another function. A function must be
defined before the function call; otherwise, the Python interpreter gives an error like function
is not defined. To call the function, use the function name followed by the parentheses.
Ex:
def hello_world(): #function definition
print("Hello Python World")

hello_world() # function calling

The return statement:


The return statement is used at the end of the function and it return the result of the
function to its caller function. It terminates the function execution and transfers the result where
the function is called. The return statement cannot be used outside of the function.
Syntax:
return expression or value or values
The return statement can contain the expression which gets evaluated and the value is
returned to the caller function. If the return statement has no expression or does not exist itself
in the function definition, then it returns None object to its caller function.

Ex1: Creating function with return statement


def sum(): # Defining function
a, b = 10, 20
return a + b
print("Sum =",sum()) # calling sum() function in print statement
3

Ex2: Creating function without return statement


def sum(): # Defining function
a, b = 10, 20
print(“Sum = “,a+b)
sum() # calling sum() function

Returning multiple values from a function:


In other languages like C, C++ and Java, function can return atmost one value only. But
in Python, a function can return any number of values.
Ex1:
def sum_sub(a, b):
sum, sub = a + b, a - b
return sum, sub
x, y = sum_sub(100,50)
print("Sum =",x,” and Subtraction =",y)
Ex2:
def calculation(a,b):
sum, sub = a+b, a-b
mul, div = a*b, a/b
return sum,sub,mul,div
result = calculation (100,30) # result type is a ‘tuple’
print("Results are: ")
for x in result:
print(x)

Arguments in function:
The arguments are types of information which can be passed to the function from the
caller function. The arguments are specified in the parentheses. We can pass any number of
arguments to a function, but they must be separated with a comma.
Ex1:
def display (name): #defining the function
print("Hi ",name)
display("Python") #calling the function

Ex2:
def sum (a, b): #defining the function
return a+b
a, b = map(int, input("Enter a and b values: ").split())
print("Addition = ",sum(a, b))

Difference between Parameters and Arguments:


Parameters refer to the variables listed in a function's definition, defining the input that
the function can accept. Arguments, however, are the actual values passed to the function when
it is called, filling the parameters during execution.
4

Parameters:
In programming, a parameter or formal parameter is a variable in a function or method
definition. It serves as a placeholder for data that will be provided when the function or method
is called. Parameters define the number and the order of values that a function or method can
accept. Parameters are used to receive the arguments passed to a function when it is called.
Ex:
def add(x, y): # Here, x and y are parameters
return x + y
Arguments:
Arguments, also known as actual arguments, are the values supplied to the parameters
of the function when it is called. These values serve as inputs to the function during its
execution. The number of arguments must match the number of parameters in the function
definition.
Ex:
result = add(5, 3) # Here, 5 and 3 are arguments

The below table summarizing the differences between arguments and parameters:

Criteria Parameters Arguments


Definition Variables defined in the function Actual values or variables passed to the
definition function
Location Part of the function definition Passed to the function when it is called
Role Define the number and order of Supply the values to the parameters of
values a function can accept a function
Naming Named identifiers used within the Values used to initialize the parameters
function in the function
Number Determined by the function Must match the number of parameters
definition in the function
Default Can have default values in some Actual values can be literals, variables,
Values languages or expressions

Call by reference in Python:


In Python, call by reference means passing the actual value as an argument in the
function. All the functions are called by reference, i.e., all the changes made to the reference
inside the function revert back to the original value referred by the reference.

Ex1: Passing mutable Object (List)


def change_list(list1):
[Link](20)
[Link](60)

list1 = [10,30,40,50] #defining the list


change_list(list1) #calling the function
print("List outside function = ",list1)
5

Ex2: Passing Immutable Object (String)


def change_string (s):
s = s + " How are you?"
print("Inside Function, S = ",s)
s = "Hi Python"
change_string(s)
print("Outside Function, S = ",s)

Packing and Unpacking Arguments:


Consider a situation where we have a function that receives four arguments. We want
to make a call to this function and we have a list of size 4 with us that has all arguments for the
function. If we simply pass a list to the function, the call doesn’t work.
Ex:
def my_function(a, b, c, d):
print(a, b, c, d)
my_list = [1, 2, 3, 4] # main function
my_function(my_list) # This doesn't work

Unpacking:
We can use * to unpack the list so that all elements of it can be passed as different
parameters. We need to keep in mind that the number of arguments must be the same as the
length of the list that we are unpacking for the arguments.
Ex:
def my_funtion(a, b, c, d):
print(a, b, c, d)
my_list = [1, 2, 3, 4] # main function
my_function(*my_list)

Types of arguments:
There may be several types of arguments which can be passed at the time of function
call.
 Required or Positional arguments.
 Default arguments.
 Keyword arguments.
 Variable length arguments.
 Variable length keyword arguments.

Required Arguments:
We can provide the arguments at the time of the function call. The required arguments
are the arguments which are required to be passed at the time of function calling with the exact
match of their positions in the function call and function definition. If either of the arguments
is not provided in the function call, or the position of the arguments is changed, the Python
interpreter will show the error.
6

Ex1:
def display(name):
message = "Hi " + name
return message
name = input("Enter the name:")
print(display(name))

Ex2:
def simple_interest(p,t,r):
return (p*t*r)/100
p = float(input("Enter the principal amount: "))
r = float(input("Enter the rate of interest: "))
t = float(input("Enter the time in months: "))
print("Simple Interest =",simple_interest(p,r,t)) # no error

Ex3:
def addition (a,b):
return a+b
print(“Sum= “,addition(10)) #causes an error as we are missing a required argument b.

Default Arguments:
Python allows us to initialize the arguments at the function definition. If the value of
any of the arguments is not provided at the time of function call, then that argument can be
initialized with the value given in the function definition.
Ex1:
def display(name, age=32):
print("My name is",name,"and age is",age)
display("Python")

Ex2:
def display(name=”Ravi”,age=32,place=”Guntur”):
print("My name is",name,", age is",age,”and my place is”,place)
display() # without passing any arguments.
display("Python”) # the variable age is not passed into the function however the
default value of age is considered in the function
display("Python",30) # the value of age is overwritten, 30 will be printed
display("Python",30,”Vijayawada”) # all default values are overwritten.

Note: After default arguments, we should not take non default(positional) arguments.
def display(name="Python",msg="Good Morning"): # Valid
def display(name,msg="Good Morning"): # Valid
def display(name="Python",msg): # Invalid
def display(name=”Python”,age,place=”Guntur”) # Invalid
7

Variable-length Arguments (*args):


Sometimes we may not know the number of arguments to be passed in advance. In such
cases, Python provides us the flexibility to offer the comma-separated values which are
internally treated as tuples at the function call. By using the variable-length arguments, we can
pass any number of arguments. At the time of function definition, we define the variable-length
argument using the * symbol.
Ex1:
def display (*names):
for name in names:
print(name)
display ("Python","Java","Oracle","DJango") #function call
display (1,2,3,4,5)
In the above code, we passed *names as variable-length argument. We called the
function and passed values which are treated as tuple internally. To print the given values, we
iterated *names using for loop.
Ex2:
def sum(*n):
total = 0
for x in n:
total = total + x
print("Sum = ",total)
sum() # prints Sum = 0
sum(10) # prints Sum = 10
sum(10,20) # prints Sum = 30
sum(10,20,30,40) # prints Sum = 100

Ex3: We can mix variable length arguments with positional arguments.


def display (n,*s):
print(n)
for x in s:
print(x)
display(10)
display(10,20,30,40)
Here, the first parameter value ‘10’ is assigned to ‘n’ and the remaining parameter
values are assigned to s.
Note: After variable length argument, if we are taking any other arguments, then we should
provide the values as keyword arguments otherwise we will get an error.
Ex:
def display (*s,n):
for x in s:
print(x)
print(n)
display ("A","B",n=10) # Valid function call
display ("A","B",10) # Invalid function call
8

Keyword Arguments:
Python allows us to call the function with the keyword arguments. This kind of function
call will enable us to pass the arguments in the random order. The name of the arguments are
treated as the keywords and they should be matched in the function calling and function
definition. If the same match is found, the values of the arguments are copied in the function
definition otherwise an error will raise.

We need to keep the following points in mind while calling functions:


1. In the case of passing the keyword arguments, the order of arguments is not important.
2. There should be only one value for one parameter.
3. The passed keyword name should match with the actual keyword name.
4. In the case of calling a function containing non-keyword arguments, the order is
important.

Ex1: Providing the values as keyword arguments with the same order in function call.
def display(name,message):
print("Printing message with",name,"and ",message)
display(name = "Python",message="Java")

Ex2: Providing the values in different order at function calling. The function
sinple_interest(p,t,r) is called with the keyword arguments, but the order of arguments
doesnot matter in this case.
def simple_interest(p,t,r):
return ((p*t*r)/100)
print("Simple Interest = ", simple_interest(t=5,r=3,p=5000))

Ex3: If we provide the different name of arguments at the time of function call, an error will
be thrown. The function simple_interest(p, t, r) is called with the keyword arguments
but with different keywords. So, it will give an error.
def simple_interest(p,t,r):
return (p*t*r)/100
print("Simple Interest: ",simple_interest(time=5,rate=3,principle_amount=5000))

Ex4:
Python also allows us to provide the mix of the required arguments and the keyword
arguments at the time of function call. However, the required arguments must not be given
after the keyword argument. Means, once the keyword argument is encountered in the function
call, the following arguments must also be the keyword arguments otherwise we will get an
error.
def display (name1,message,name2):
print("printing the message with",name1,” “,message,",and",name2)
display("Python",message="hello",name2="Java") #valid function call
9

Ex5: The following example will cause an error due to an in-proper mix of keyword
arguments and required arguments being passed in the function call.

def display(name1,message,name2):
print("printing the message with",name1,",",message,", and",name2)
display ("Python",message="Hello","Java") # Error will get

Variable length keyword arguments(**kwargs):


Python provides the facility to pass multiple keyword arguments which can be
represented as **kwargs. This type of argument is useful when we do not know the number of
arguments should be pass in advance. Internally these keyword arguments will be stored inside
a dictionary. We can call this function by passing any number of keyword arguments.
Ex1:
def display(**kwargs):
for keys,values in [Link]():
print(keys,"=",values)

display(n1=10,n2=20,n3=30)
display(rno=100,name="Anandh",marks=70,subject="Python")

Ex2: Python program to illustrate **kwargs for variable number of keyword arguments
with one extra argument.
def display(arg, **kwargs):
print(arg)
for key, value in [Link]():
print ("%s = %s" %(key, value))
display("Hi", first ='Python', mid ='Programming', last='Language')
Note:
We cannot take required positional arguments or keyword arguments or even variable
length arguments after the variable length keyword arguments. But before variable length
keyword arguments we can take any type of arguments.

Ex3: Using *args and **kwargs in same line to call a function


def display(*args,**kwargs):
print("args: ", args)
print("kwargs: ", kwargs)
display('Python','Programming','Language',first="C",mid="C++",last="Java")

Scope of variables:
The scope of the variable depends upon the location where the variable is being
declared. The variable declared in one part of the program may not be accessible to the other
parts of the program. In python, the variables are defined with two types of scopes.
 Global variables and Local variables
10

The variables which are defined outside of all the functions are known to have a global
scope and whereas the variables which are defined inside a function are known to have a local
scope.
Ex1: Local Variable
def display():
message = "Hello
Python" # the variable message is local to the function itself
print(message)
display()
print(message) # this causes an error since a local variable cannot be accessible here.

Ex2: Global Variable


def display_sum(*args):
sum = 0
for x in args:
sum = sum + x
print("Sum = ",sum) #60 will be printed as the sum
sum = 0
display_sum(10,20,30)
print("Value of sum outside the function =",sum) # 0 will be printed

global keyword:
Python provides the global keyword that is used to modify the value of the global
variable inside the function. It is beneficial when we want to change the value of the global
variable or assign some other value inside a function.

Rules of global Keyword:


 If the variable is defined outside of the function, it will automatically become the global
variable and its scope is globally.
 The global keyword is used to declare the global variable inside a function.
 We don't need to use the global keyword to declare a global variable outside the
function.
 Variables that have global reference inside a function are implicitly global.
We can use global keyword for the following 2 purposes:
1. To declare a variable as a global variable inside the function definition.
2. To make global variable available to the function so that we can perform required
modifications.
Ex1:
a = 10
def display1():
a = 20
print(a)
11

def display2():
print(a)
display1() # prints 20
display2() # prints 10
Ex2:
a = 10
def display1():
global a
a = 20
print(a)
def display2():
print(a)
display1() # prints 20
display2() # prints 20
Ex3:
def display1():
a = 10
print(a)
def display2():
print(a)
display1() # prints 10
display2() # NameError: name 'a' is not define
Ex4:
def display1():
global a
a = 10
print(a)
def display2():
global a
a = a + 10
print(a)
display1() # prints 10
display2() # prints 20

Note:
If global variable and local variable having the same name then we can access global
variable inside a function as follows.
Ex:
a = 10 #global variable
def display():
a = 20 #local variable
print(a) #prints the value of local variable
print(globals()['a']) #prints the value of global variable
display()
12

Recursion in Python:
Like C, C++ and Java, Python also supports recursive functions. Recursion is said to
be the process of repeating things in a similar manner. In computer science, recursion is a
process of calling a function itself within its own code. Any function which calls itself is called
a recursive function, and such type of function calls are called recursive function calls.
During defining the recursive function, we must define an exit condition carefully;
otherwise, it will go to an infinite loop. So, it is important to specify a termination condition of
recursion. It is slower than iteration because of the overhead of maintaining of the stack.
Recursion code is shorter than iterative code; however, it is difficult to understand. Recursive
functions are helpful in solving various problems such as finding the factorial of a number,
creating the Fibonacci series, and searching an item in a sequence etc.

Advantages:
 We can reduce the length of the code and improves readability
 We can solve complex problems very easily.

Ex1: def factorial(n):


if n == 1: return 1
else: return n*factorial(n-1)
print("Factorial of 5 is :",factorial(5))

Ex2: Linear search program using recursion


def linear_search(mylist,value,position,size):
if position<0 or position>=size: print(value,"not found")
elif value==mylist[position]: print(value,"found")
else: linear_search(mylist,value,position+1,size)
mylist = [5,8,2,9,1,4,3,6,7,8]
value = int(input("Enter search value: "))
linear_search(mylist,value,0,len(mylist))

Ex3: Binary search program using recursion


def binary_search(mylist, lb, ub, number):
if lb<=ub:
mid = (lb + ub) // 2
if mylist [mid] == number: return mid
elif mylist [mid] > number:
return binary_search(mylist, lb, mid-1, number)
else: return binary_search(mylist, mid+1, ub, number)
else: return -1
mylist = [5, 7, 9, 12, 14, 16, 17, 19, 22, 24, 26, 30]
number = int(input(“Enter a value to search: “))
index = binary_search(mylist, 0, len(mylist)-1, number)
if index != -1: print("Element found at", index,”index”)
else: print("Element is not present in the list")
13

Difference Between Recursion and Iteration:


Recursion Iteration
The code size in recursion is smaller than The code size in iteration is larger than the
the code size in iteration. code size in recursion.
It is always applied to functions. It is applied to loops.
It is slower than iteration. It is faster than recursion.
Recursion is generally used where there is It is used when we have to balance the time
no issue of time complexity, and code size complexity against a large code size.
requires being small.
It has high time complexity. The time complexity in iteration is relatively
lower.
It has to update and maintain the stack. There is no utilization of stack.
It uses more memory as compared to It uses less memory as compared to recursion.
iteration.
There is an extensive overhead due to There is no overhead in iteration.
updating and maintaining the stack.

Anonymous or Lambda Functions:


Sometimes we can declare a function without any name, such type of nameless
functions are called anonymous functions or lambda functions. The main purpose of
anonymous function is just for instant usage (for one time use). The anonymous function
contains a small piece of code. However, Lambda functions can accept any number of
arguments, but they can return only one value in the form of an expression and we are not
required to write the return statement explicitly.
By using lambda functions, we can write very concise code so that readability of the
program will be improved. Sometimes we can pass function as an argument to another function.
In such cases lambda functions are best choice. We can use lambda functions very commonly
with filter(), map() and reduce() functions, because these functions expect a function as an
argument.

Syntax:
variable_name = lambda arguments_list: expression
Here, the variable_name is used as a function name to call the lambda function.
Ex1:
sum = lambda a, b: a + b
print("Sum of 10 and 20 is:", sum(10, 20))
Ex2:
square = lambda n: n*n
print("The Square of 4 is :",square(4))
Ex3:
sum = lambda a, b: (a + b, a – b, a * b, a / b)
x, y, z, d = sum(20,3)
print(f"Sum = {x}\nSub = {y}\nPro = {z}\nDiv = {d}")
14

Nested Lambda Functions:


In Python, anonymous function means that a function is without a name. As we already
know the def keyword is used to define the normal functions and the lambda keyword is used
to create anonymous functions. When we use lambda function inside another lambda function
then it is called Nested Lambda Function.
Ex1:
fun = lambda a = 2, b = 3:lambda c: a+b+c
obj = fun() # You can also pass 2 arguments like ‘obj = fun(10, 20)’
print(obj(4))
Here, when the object ‘obj’ with parameter 4 is called, the control shift to fun() which is
caller object of the whole lambda function. Then the following execution takes place.
 The nested lambda function takes the value of a and b from the first lambda function as
a=2 and b=3.
 It takes the value of c from its caller object obj which passes c = 4.
 Finally, we get the output which is the summation of a, b and c that is 9.

Ex2:
square = lambda x: x**2
product = lambda f, n: lambda x: f(x)*n
ans = product(square, 2)(10)
print(ans)
In the above example, when the product function is called, square function gets bound
to f and 2 gets bound to n which then returns a function which is bound to the product which
when called with 10, x is assigned this and square is called, which returns 100 and this, in turn,
is multiplied with n which is 2. So, it’ll finally return 200.

filter() Function:
Python filter() function is used to get the filtered elements from a sequence object. This
function takes two arguments; first is a function and the second is an iterable. The filter function
returns a sequence from those elements of iterable object for which the function returns True.
The filter() returns an iterator, so you need to convert it to a list, tuple, or another iterable to
see the results. If the function passed to filter() is None, it removes all False or Zero values
from the iterable.
Syntax:
filter(function, sequence)

Ex1: Program to filter only even numbers from the list by using filter() function:
 Without lambda function:
def isEven(x):
if x%2==0:
return x // we can also use; return True
mylist = [2,5,10,15,20,25,30]
even_list = list(filter(isEven, mylist))
print(even_list) # prints [2,10, 20, 30]
15

 With lambda function:


mylist = [3,5,10,15,20,25,30]
even_list = list(filter(lambda x:x%2==0, mylist))
odd_list = list(filter(lambda x:x%2!=0, mylist))
print(“Even List = “,even_list) #[10,20,30]
print(“Odd List = “,odd_list) #[3,5,15,25]

Ex2: The first argument can be None if the function is not available and returns only elements
that are True.
result1 = list(filter(None,(1,0,6))) # returns all non-zero values
result2 = list(filter(None,(1,0,False,True))) # returns all non-zero and True values
print(result1, result2)

Ex3: Filter elements based on a custom function


def is_prime(n):
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
numbers = [2, 3, 4, 5, 6, 7, 8, 9, 10]
prime_numbers = list(filter(is_prime, numbers))
print(prime_numbers) # Output: [2, 3, 5, 7]

Ex4: Filter elements from a list of dictionaries


people = [ {"name": "Alice", "age": 25}, {"name": "Bob", "age": 17},
{"name": "Charlie", "age": 30}]
adults = filter(lambda person: person["age"] >= 18, people)
print(list(adults)) # Output: [{'name': 'Alice', 'age': 25}, {'name': 'Charlie', 'age': 30}]

Ex5: Combine filter() and map()


numbers = [1, 2, 3, 4, 5, 6]
even_squares = list(map(lambda x: x**2, filter(lambda x: x % 2 == 0, numbers)))
print(even_squares) # Output: [4, 16, 36]

map() function:
The python map() function is used to return a list of results after applying a given
function to each item of an iterable(list, tuple etc.). For every element present in the given
sequence, apply some functionality and generate new element with the required modification.
For example, for every element present in the list perform double and generate new list of
doubles. map() returns an iterator, so you need to convert it to a list, tuple, or another iterable
to see the results.
16

Syntax:
map(function, sequence)

Ex1: Without lambda:


mylist = [1,2,3,4,5]
def doubleIt(x):
return 2*x
mylist1 = list(map(doubleIt, mylist))
print(mylist1) #[2, 4, 6, 8, 10]

With lambda:
mylist = [1,2,3,4,5]
mylist1 = list(map(lambda x:2*x, mylist))
print(mylist1) #[2, 4, 6, 8, 10]

Ex2: To double the numbers of a tuple


mylist = [1, 2, 3, 4, 45]
result = list(map(lambda x: x**3, mylist))
print(result)

Ex3: To find square of given numbers


mylist = [1,2,3,4,5]
mylist1 = list(map(lambda x:x*x, mylist))
print(mylist1) #[1, 4, 9, 16, 25]

Ex4: We can apply map() function on multiple lists also. But make sure all lists should have
the same length.
Syntax:
map(lambda x, y: x*y, x1, y1)) # here x is from x1 and y is from y1 lists

list1, list2 = [1,2,3,4], [2,3,4,5]


list3 = list(map(lambda x, y: x*y, list1, list2))
print(list3) #[2, 6, 12, 20]

Ex5: Convert strings to uppercase


words = ["hello", "world", "python"]
uppercase_words = list(map([Link], words))
print(uppercase_words) # Output: ['HELLO', 'WORLD', 'PYTHON']

Ex6: Use map() with multiple iterables


list1, list2, list3 = [1, 2, 3], [10, 20, 30], [100, 200, 300]
result = map(lambda x, y, z: x + y + z, list1, list2, list3)
print(list(result)) # Output: [111, 222, 333]
17

Ex7: Use map() with a list of dictionaries


people = [{"name": "Alice", "age": 25}, {"name": "Bob", "age": 30}]
names = map(lambda person: f"Name: {person['name']}, Age:
{person['age']}",people)
for name in names:
print(name)

reduce() Function:
In Python, reduce() is a built-in function that applies a given function to the elements
of an iterable, reducing them to a single value. The reduce(fun,sequence) function is used
to apply a particular function passed in its argument to all of the list elements mentioned in the
sequence passed along. This function is defined in “functools” module.

Syntax:
reduce(function, iterable[, initializer])
 The function argument is a function that takes two arguments and returns a single value.
The first argument is the accumulated value, and the second argument is the current
value from the iterable.
 The iterable argument is the sequence of values to be reduced.
 The optional initializer argument is used to provide an initial value for the accumulated
result. If no initializer is specified, the first element of the iterable is used as the initial
value.

Ex1: Without lambda function


from functools import reduce
def addition(x, y):
return x + y
my_list = [1, 2, 3, 4, 5]
result = reduce(addition, my_list)
print(result)

Ex1: With lambda function


from functools import reduce
my_list = [1, 2, 3, 4, 5]
result = reduce(lambda x, y: x + y, my_list)
print(result)

Ex2: from functools import reduce


num_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
sum = reduce(lambda a, b: a + b, num_list)
print(f"Sum without initial value = {sum}")
sum = reduce(lambda a, b: a + b, num_list, 10)
print(f"Sum with initial value 10 = {sum}")
18

Ex3: Find the maximum element in a list


from functools import reduce
numbers = [10, 20, 5, 30, 15]
max_result = reduce(lambda x, y: x if x > y else y, numbers)
print(max_result) # Output: 30

Ex4: Flatten a list of lists


from functools import reduce
lists = [[1, 2, 3], [4, 5], [6, 7, 8]]
flattened_list = reduce(lambda x, y: x + y, lists)
print(flattened_list) # Output: [1, 2, 3, 4, 5, 6, 7, 8]

Ex5: Factorial of a number


from functools import reduce
n = int(input() # Consider n = 5
factorial_result = reduce(lambda x, y: x * y, range(1, n+1))
print(factorial_result) # Output: 120

Function Aliasing:
In python programming, the second name given to a piece of data is known as an alias.
Aliasing happens when the name of one function is assigned to another function because
functions are just names that store references to actual function code. For the existing function,
we can give another name and which is nothing but function aliasing.
Ex1:
def wish(name):
print("Good Morning:",name)
greeting = wish
print(id(wish), id(greeting))
greeting('Python')
wish('Python')
In the above example, only one function is available but we can call that function by
using either “wish” name or “greeting” name.
Note:
If we delete one name, still we can access that function by using alias name.

Ex2: def wish(name):


print("Good Morning:",name)
greeting = wish
greeting('Python')
wish('Python')
del wish
wish(''Python”) #NameError: name 'wish' is not defined
greeting('Python')
19

Nested or Inner Functions:


We can declare a function inside another function, such type of functions are called
nested functions or inner functions.
Ex:
def outer_function():
print("Outer function started")
def inner_function():
print("Inner function started")
inner_function() # calling inner function
outer_function()
inner_funtion() # NameError: name 'inner_function' is not defined.
In the above example, inner_function() is local to the outer_function() and hence it is
not possible to call directly from outside of the outer_function().

Ex: A function can return another function.


def outer_function():
print("Outer function started")
def inner_function():
print("Inner function started")
return inner_function
inner_function = outer_function()
inner_function()
Here, we are calling outer_function(), which returns inner function. For that inner
function we are providing another name inner_function and we are calling that inner function
with the name inner_function.

nonlocal Keyword:
The nonlocal keyword is used to declare that a variable inside a nested function (a
function within another function) is not local to that nested function, but rather belongs to the
nearest enclosing scope that is not global. This allows you to modify a variable in the enclosing
scope from within the nested function.

Key Points:
 nonlocal is used in nested functions.
 It allows you to assign a value to a variable in the nearest enclosing scope (excluding
the global scope).
 If the variable is not found in any enclosing scope, a SyntaxError will be raised.
Ex:
def outer_function():
x = 10 # This is in the enclosing scope
def inner_function():
nonlocal x # Declare x as nonlocal
x = 20 # Modify x in the enclosing scope
print("Inner function:", x)
20

inner_function()
print("Outer function:", x)

outer_function()

Explanation:
 x is defined in the outer_function scope.
 In inner_function, nonlocal x tells Python that x refers to the x in the nearest enclosing
scope (i.e., outer_function).
 When x is modified in inner_function, it changes the value of x in outer_function.

Difference Between nonlocal and global:


 nonlocal is used for variables in the nearest enclosing scope (not global).
 global is used for variables in the global scope.

Ex: Example with global:


x = 10 # Global variable
def outer_function():
def inner_function():
global x # Declare x as global
x = 20 # Modify x in the global scope
inner_function()
print("Outer function:", x)
outer_function()
print("Global scope:", x)

When to Use nonlocal:


 When you need to modify a variable in the enclosing (but not global) scope from within
a nested function.
 If you don't use nonlocal, Python will treat the variable as local to the nested function,
and any assignment will create a new local variable instead of modifying the enclosing
scope's variable.
Ex: Example Without nonlocal:
def outer_function():
x = 10
def inner_function():
x = 20 # This creates a new local variable x
print("Inner function:", x)
inner_function()
print("Outer function:", x)
outer_function()
Here, x in inner_function is a new local variable, so the x in outer_function remains
unchanged.
21

Python Closures:
Python closure is a nested function that allows us to access variables of the outer
function even after the outer function is closed.
Ex1:
def greet():
name = "Python" # variable defined outside the inner function
return lambda: "Hi " + name # return a nested anonymous function
message = greet() # call the outer function
print(message()) # call the inner function

In the above example, we have created a function named greet() that returns a
nested anonymous function. Here, when we call the outer function, message = greet(). The
returned function is now assigned to the message variable. At this point, the execution of the
outer function is completed, so the name variable should be destroyed.
However, when we call the anonymous function using print(message()), we are able to
access the name variable of the outer function. It is possible because the nested function now
acts as a closure that closes the outer scope variable within its scope even after the outer
function is executed.
Ex2:
def fun1(x):
def fun2(y):
return x + y
return fun2
closure = fun1(10)
print(closure(5))

Explanation:
 Outer Function (fun1): Takes an argument x and defines the fun2. The fun2 uses x
and another argument y to perform a calculation.
 Inner Function (fun2): This function is returned by fun1 and is thus a closure. It
“remembers” the value of x even after fun1has finished executing.
 Creating and Using the Closure: When you call fun1(10), it returns fun2 with x set
to 10. The returned fun2(closure) is stored in the variable closure. When you call
closure(5), it uses the remembered value of x (which is 10) and the passed argument y
(which is 5), calculating the sum 10 + 5 = 15.

Ex3: Print Odd Numbers using Python Closure


def calculate():
num = 1
def inner_func():
nonlocal num
num += 2
return num
return inner_func
22

odd = calculate() # call the outer function


print(odd()) # call the inner function
print(odd())
print(odd())
odd2 = calculate() # call the outer function again
print(odd2())

Higher Order or First-Class Functions:


A higher-order function can be defined as a function that accepts one or more functions
as an argument and return a function as a result. Using higher-order functions in our code
enhances the execution speed of our code and speed up our development skills. Higher order
function is applicable to both the functions as well as to the methods that take a function as
their parameter or return a function as the result of them.
Properties of Higher Order Functions:
Some of the important properties of high order functions that are applicable in Python
are as follows.
 In high order function, we can store a function inside a variable.
 In high order function, a function can act as an instance of an object type.
 In high order function, we can return a function as result of another function.
 In high order function, we can pass a function as a parameter or an argument inside
another function.
 We can store Python high order functions in data structures format such as lists, hash
tables, etc.

Ways of defining higher order functions:


In python programming, we can define a higher order function in the following ways:
 Functions as objects in High order function.
 Returning function as a result in high order function.
 Functions as a parameter for another function.
 Decorator functions as high order function.

Function as object in high order function:


In Python, we can even assign a given function to a variable also. This assignment of
function to a variable will not call the actual function, instead of that, it will create a reference
to the function that is created. Thus, it makes this assignment of assigning a function as a
variable object will create a high order function in the program.
Ex:
def display(msg):
return [Link]()
text = input("Enter a text: ")
print(display(text))
my_function = display
print(my_function(text))
23

Function as a parameter for another function:


Basically, Python functions are like Python objects, and therefore we can use Python
functions to pass them as an argument inside another function, and that will create a high order
function in the program.
Ex:
def string_lower(string):
return “String in lowercase = “ + [Link]()
def string_upper(string):
return “String in uppercase = “ + [Link]()
def display_string(function):
text = function("Welcome To Python")
print(text)
display_string(string_lower)
display_string(string_upper)

Returning function as a result in high order function:


We can also return a function as the result of another function as an object, and that
makes the function a high order function.
Ex: def add1(a):
def add2(b):
return a + b
return add2
a = int(input("Enter First Number: "))
b = int(input("Enter Second Number: "))
add = add1(a)
result = add(b)
print("Sum of two numbers = ", result)

Python Decorators (Decorators as high order function):


We can use decorators as the high order function is the most commonly used high order
function in Python. Decorators allow us to modify the behavior of methods or functions we
defined in the program, and it also allows us to wrap a function inside another function to
extend the behavior of wrapped or parent function without even permanently modifying the
parent function.
In decorators, a function is taken as an argument for the other function, and then these
decorators are called inside the wrapped function. The main objective of decorator function is;
we can extend the functionality of existing function without permanently modifying that
function. It is also called meta programming where a part of the program attempt to change
another part of the program at compile time.
Python has the most interesting feature that everything is treated as an object even
classes or any variable we define in Python is also assumed as an object. Functions are first-
class objects in the Python because they can reference to a variable, passed to a variable and
returned from other function as well.
24

Ex:
def division_decorator(division):
def inner_division(x,y):
if(x<y):
x,y = y,x
division(x,y)
else:
division(x,y)
return inner_division
def divide(x,y):
print(x/y)
division = division_decorator(divide)
division(2,4)

Syntactic Decorators:
In the above program, we have decorated division_decorator() that is little bit difficulty
to understand. Instead of using above method, Python allows to use decorator in easy way with
@ symbol. Sometimes it is called "pie" syntax.
Ex1:
def decorator_division(division):
def inner_division(x,y):
if(x<y):
x,y = y,x
division(x,y)
return inner_division
@decorator_division
def division(x,y):
print(x/y)
division(2,9)
Ex2:
def decorator_display(display):
def decorator_inner(name):
if name=="Python":
print("Hello Python Language")
25

elif name==’Django’:
print(“Hai Django Frame Work”)
else:
display(name)
return decorator_inner
@decorator_display
def display(name):
print("Hello",name,"Good Morning")

display("Java")
display("Python")
display("Django")
display(“C-Lang”)
In the above program, whenever we call display() function then automatically
decorator_display() function will be executed.

Ex3:
def decorator_division(division):
def inner_division(a,b):
if b==0:
b = int(input("Enter b value: "))
division(a,b)
elif b>a:
a,b = b,a
division(a,b)
else:
division(a,b)
return inner_division

@decorator_division
def division(a,b):
print("Result = ",a/b)

division(3,9);
division(20,3);
division(10,0)

Decorator Chaining:
We can define multiple decorators for the same function and all these decorators will
form Decorator Chaining. Decorators chaining means applying more than one decorator inside
a function. Python allows us to implement more than one decorator to a function. It makes
decorators useful for reusable building blocks as it accumulates the several effects together. It
is also known as nested decorators in Python.
26

Ex1:
def decorator_division1(division): # Decorator function1
def inner_division1(a,b):
if b==0:
b = int(input("Enter b value: "))
division(a,b)
else:
division(a,b)
return inner_division1

def decorator_division2(division): # Decorator function2


def inner_division2(a,b):
if b>a:
a,b = b,a
division(a,b)
else:
division(a,b)
return inner_division2

@decorator_division1 # Applying decorator functions


@decorator_division2
def division(a,b):
print("Result = ",a/b)

division(3,9)
division(20,3)
division(10,0)

Ex2:
def decorator_display1(display):
def inner_decorator1(name):
if name=='Java':
print("Hello",name,"Good Morning")
elif name=='Django':
print("Hello",name,"Good Evening")
else:
display(name)
return inner_decorator1

def decorator_display2(display):
def inner_decorator2(name):
if name=='Python':
print("Hello",name,"Good Afternoon")
elif name=='Oracle':
27

print("Hello",name,"Good Night")
else:
display(name)
return inner_decorator2

@decorator_display1
@decorator_display2
def display(name):
print("Name = ",name)

display("Java"); display("Python")
display("Oracle"); display("Django")

Generators:
In Python, a generator is a function that returns an iterator that produces a sequence of
values when iterated over. Generators are useful when we want to produce a large sequence of
values, but we don't want to store all of them in memory at once.
We can write generator function just like an ordinary function, but it uses yield keyword
to return values. We can use the multiple yield statements in the generator function. The return
statement returns a value and terminates the whole function and only one return statement can
be used in the function. We can say that if the body of any function contains a yield statement,
it automatically becomes a generator function.
Creating a generator in Python is as simple as defining a function with at least one yield
statement. When called, this function doesn’t return a single value; instead, it returns a
generator object that supports the iterator protocol.

yield vs return Keywords:


The yield is used in generator functions to provide a sequence of values over time.
When yield is executed, it pauses the function, returns the current value and retains the state of
the function. This allows the function to continue from the same point when called again,
making it ideal for generating large or complex sequences efficiently. The return, on the other
hand, is used to exit a function and return a final value. Once return is executed, the function
is terminated immediately, and no state is retained. This is suitable for cases where a single
result is needed from a function.

Advantages:
 When compared with class level iterators, generators are very easy to use.
 Improves memory utilization and performance.
 Generators are best suitable for reading data from large number of files.
 Generators work great for web scraping and crawling.
28

Syntax:
def generator_function_name(parameters):
# Your code here
yield expression
# Additional code can follow

Ex1:
def mygen():
yield 'A' # Using for loop
yield 'B' def mygen(): # Generate alphabets
yield 'C' yield 'A' def gen_char():
yield 'D' yield 'B' for i in range(65,91):
g = mygen() yield 'C' yield chr(i)
print(next(g)) yield 'D'
print(next(g)) for ch in gen_char():
print(next(g)) for ch in mygen(): print(ch,end=' ')
print(next(g)) print(ch)

Ex2: from time import sleep


def countdown(second):
print("Countdown started..")
while second>0:
yield second
second = second -1

seconds = countdown(10)
for i in seconds:
print(i)
sleep(1)
else:
print("Game Over")

Ex3: To generate first n numbers:


def display_numbers(num):
n=1
while n<=num:
yield n
n = n+1

n = int(input(“Enter n value: “))


values = display_numbers(n)
for x in values:
print(x)
Note: We can convert generator into list or tuple by using list() and tuple() functions as
follows:
print("Type of list = ",type(list(values)))
29

Ex4: To generate N Fibonacci Numbers.


def fibonocci(n):
a, b, i = 0, 1, 1
while i<=n:
c = a+b
yield c
a,b = b,c
i=i+1

n = int(input(“Enter n value: “))


list = fibonocci(n)
for i in list:
print(i)

Ex5: Write a program to print the table of the given number using the generator.
def math_table(n):
for i in range(1,21):
yield str(int(n))+" * "+str(int(i))+" = "+str(int(n*i))

n = int(input(“Enter n value: “))


for i in math_table(n):
print(i)

Ex6: Generate Infinite Number Sequence.


The generator can produce infinite items. Infinite sequences cannot be contained within
the memory and since generators produce only one item at a time.
def infinite_sequence():
num = 0
while True:
yield num
num += 1
for i in infinite_sequence():
print(i)

Difference between Generator function and Normal function:


 Normal function contains only one return statement whereas generator function can
contain one or more yield statement.
 When the generator functions are called, the normal function is paused immediately
and control transferred to the caller.
 Local variable and their states are remembered between successive calls.
 StopIteration exception is raised automatically when the function terminates.
30

Generator Expression:
We can easily create a generator expression without using user-defined function. It is
the same as the lambda function which creates an anonymous function. The generator's
expressions create an anonymous generator function. Generator expressions are a concise way
to create generators. The representation of generator expression is similar to the Python list
comprehension. The only difference is that square bracket is replaced by round parentheses
and are more memory efficient. The list comprehension calculates the entire list, whereas the
generator expression calculates one item at a time.

Ex1:
list = [1,2,3,4,5,6,7]
z = [x**3 for x in list] # List Comprehension
a = (x**3 for x in list) # Generator expression
print(a, z,sep = ‘\n’)

Ex2: In the above program, list comprehension has returned the list of cube of elements
whereas generator expression has returned the reference of calculated values. Instead of
applying a for loop, we can also call next() function on the generator object.
list = [1,2,3,4,5,6]
z = (x**3 for x in list)
print(next(z))
print(next(z))
print(next(z))
print(next(z))

Ex3: To generate even numbers from 1 to ‘N’.


n = int(input("Enter n: "))
my_generator = ( i for i in range(1,n+1) if i%2==0)
for i in my_generator:
print(i,end=' ')

Ex4: To generate ‘N’ number of characters.


n = int(input("Enter n: "))
if n>=1 and n<=26:
my_alphabets = (chr(i+65) for i in range(n))
for ch in my_alphabets:
print(ch,end=' ')
else:
print("Wrong number")
31

Function Annotations in python:


Function annotations are a way to attach metadata to the parameters and return value of
a function. They are optional, meaning they don't affect the execution of the function. Instead,
they provide additional information about the function's expected input types, return types, or
other metadata. Annotations are a powerful feature for improving code readability,
maintainability, and tooling support, especially in larger projects. Function annotations are
stored in the __annotations__ attribute of the function and can be accessed at runtime.

Key Points:
 Function annotations provide a way to attach metadata to function parameters and
return values.
 They are optional and do not affect the runtime behaviour of the function.
 Commonly used for type hints, documentation, and custom metadata.
 Annotations can be accessed via the __annotations__ attribute of the function.

Syntax of Function Annotations:


Annotations are specified using a colon (:) after the parameter name or the -> symbol
before the colon for the return type. The annotation can be any valid Python expression.
Syntax:
def function_name(param1: annotation1, param2: annotation2) -> return_annotation:
# Function body
Ex:
def greet(name: str, age: int) -> str:
return f"Hello, {name}. You are {age} years old."
In this example:
 name: str indicates that the name parameter is expected to be of type str.
 age: int indicates that the age parameter is expected to be of type int.
 -> str indicates that the function is expected to return a value of type str.

Accessing Annotations:
You can access the annotations of a function using the __annotations__ attribute:
Ex:
print(greet.__annotations__)
Output:
{'name': <class 'str'>, 'age': <class 'int'>, 'return': <class 'str'>}

Use Cases for Function Annotations


1. Type Hinting:
Annotations are commonly used for type hints, which help developers understand the
expected types of function arguments and return values. Tools like mypy or IDEs can
use these hints for static type checking.
Ex:
def add(a: int, b: int) -> int:
return a + b
32

2. Documentation:
Annotations can provide additional information about the purpose or constraints of
parameters and return values.
def divide(dividend: float, divisor: "non-zero value") -> float:
return dividend / divisor
3. Custom Metadata:
Annotations can store any metadata, not just type hints. For example, you could
annotate parameters with units of measurement or validation rules.
def calculate_area(length: "meters", width: "meters") -> "square meters":
return length * width

Annotations Are Optional and Not Enforced:


Python does not enforce annotations at runtime. They are purely informational and do
not affect the behaviour of the function.
def add(a: int, b: int) -> int:
return a + b
result = add(3.5, 4.2) # This will work even though the arguments are not integers
print(result) # Output: 7.7

Combining Annotations with Default Values


You can combine annotations with default values for parameters:
Ex:
def greet(name: str = "Guest", age: int = 18) -> str:
return f"Hello, {name}. You are {age} years old."

Annotations for Complex Types:


You can use annotations for more complex types, such as lists, dictionaries, or custom
classes:
Ex:
from typing import List, Dict
def process_data(data: List[Dict[str, int]]) -> Dict[str, float]:
# Process data and return a dictionary
return {"average": 42.0}

Limitations of Function Annotations:


1. No Runtime Enforcement:
Annotations are not enforced by Python, so they don't prevent incorrect types from
being passed to a function.
2. Tool Dependency:
To benefit from annotations (e.g., for type checking), you need to use external tools
like mypy or IDE features.
 Verbosity:
Annotations can make function signatures longer and more complex, especially for
functions with many parameters or complex types.

You might also like