Python Functions
Python Functions
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.
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")
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))
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:
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
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.
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
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.
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.
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.
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.
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
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
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)
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)
With lambda:
mylist = [1,2,3,4,5]
mylist1 = list(map(lambda x:2*x, mylist))
print(mylist1) #[2, 4, 6, 8, 10]
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
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.
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.
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.
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.
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
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.
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)
seconds = countdown(10)
for i in seconds:
print(i)
sleep(1)
else:
print("Game Over")
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))
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))
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.
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'>}
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