Sitemap

Chapter-4: Functions in Python

--

#SeekhoPython

Functions in Python provide organized, reusable and modular code to perform a set of specific actions. Functions simplify the coding process, prevent redundant logic, and make the code easier to follow. This topic describes the declaration and utilization of functions in Python.

Python has many built-in functions like print(), input(), len(). Besides built-ins you can also create your own functions to do more specific jobsƒthese are called user-defined functions.

Using the def statement is the most common way to define a function in python. This statement is a so called single clause compound statement with the following syntax:

def function_name(parameters):
statement(s)

function_name is known as the identifier of the function. Since a function definition is an executable statement its execution binds the function name to the function object which can be called later on using the identifier.

parameters — is an optional list of identifiers that get bound to the values supplied as arguments when the function is called. A function may have an arbitrary number of arguments which are separated by commas.

statement(s) — also known as the function body „ are a nonempty sequence of statements executed each time the function is called. This means a function body cannot be empty, just like any indented block. Here€s an example of a simple function definition which purpose is to print Hello each time it’s called:

def greet():
print("Hello")

Now let€s call the defined greet() function:

greet()
# Out: Hello

That’s another example of a function definition which takes one single argument and displays the passed in value each time the function is called:

def greet_two(greeting):
print(greeting)

After that the greet_two() function must be called with an argument:

greet_two("Howdy")
# Out: Howdy

Also you can give a default value to that function argument:

def greet_two(greeting="Howdy"):
print(greeting)

Now you can call the function without giving a value:

greet_two()
# Out: Howdy

You’ll notice that unlike many other languages, you do not need to explicitly declare a return type of the function. Python functions can return values of any type via the return keyword. One function can return any number of different types!

def many_types(x):
if x < 0:
return "Hello!"
else:
return 0
print(many_types(1))
print(many_types(-1))

# Output:
0
Hello!

As long as this is handled correctly by the caller, this is perfectly valid Python code.

A function that reaches the end of execution without a return statement will always return None:

def do_nothing():
pass
print(do_nothing())
# Out: None

As mentioned previously a function definition must have a function body, a nonempty sequence of statements. Therefore the pass statement is used as function body, which is a null operation „ when it is executed, nothing happens. It does what it means, it skips. It is useful as a placeholder when a statement is required syntactically, but no code needs to be executed.

Defining a function with an arbitrary number of arguments:

Arbitrary number of positional arguments:

Defining a function capable of taking an arbitrary number of arguments can be done by prefixing one of the arguments with a *

def func(*args):
# args will be a tuple containing all values that are passed in
for i in args:
print(i)
func(1, 2, 3) # Calling it with 3 arguments
# Out: 1
# 2
# 3


list_of_arg_values = [1, 2, 3]
func(*list_of_arg_values) # Calling it with list of values, * expands the list
# Out: 1
# 2
# 3
func() # Calling it without arguments
# No Output

You can’t provide a default for args, for example func(*args=[1, 2, 3]) will raise a syntax error (won’t even compile).

You can’t provide these by name when calling the function, for example func(*args=[1, 2, 3]) will raise a TypeError.

But if you already have your arguments in an array (or any other Iterable), you can invoke your function like this: func(*my_stuff).

These arguments (*args) can be accessed by index, for example args[0] will return the first argument.

Arbitrary number of keyword arguments:

You can take an arbitrary number of arguments with a name by defining an argument in the definition with two * in front of it:

def func(**kwargs):
# kwargs will be a dictionary containing the names as keys and the values as values
for name, value in kwargs.items():
print(name, value)
func(value1=1, value2=2, value3=3) # Calling it with 3 arguments
# Out: value1 1
# value2 2
# value3 3
func() # Calling it without arguments
# No Out put
my_dict = {'foo': 1, 'bar': 2}
func(**my_dict) # Calling it with a dictionary
# Out: foo 1
# bar 2

You can’t provide these without names, for example func(1, 2, 3) will raise a TypeError.

kwargs is a plain native python dictionary. For example, args[‘value1’] will give the value for argument value1. Be sure to check beforehand that there is such an argument or a KeyError will be raised.

Warnings:

You can mix these with other optional and required arguments but the order inside the definition matters.

  1. The positional/keyword arguments come first. (Required arguments).
  2. Then comes the arbitrary *arg arguments. (Optional).
  3. Then keyword-only arguments come next. (Required).
  4. Finally the arbitrary keyword **kwargs come. (Optional).
#       |-positional-|-optional-|---keyword-only--|-optional-|
def func(arg1, arg2=10 , *args, kwarg1, kwarg2=2, **kwargs):
pass

NESTED FUNCTIONS:

A nested function is a function defined inside another function. The inner function can use variables from the outer function, making it useful when you want to structure code more efficiently.

def greetFunction(language="EN"):  # Outer function
greetings = {"EN": "Hello", "FR": "Bonjour", "ES": "Hola"} # Dictionary of greetings

def greet(name): # Inner function
print(f"{greetings[language]} {name}, Welcome to the world of Python!")

return greet # Returning the inner function


greet = greetFunction() # Calling greetFunction, which returns the greet function
greet("Chanchal") # Output: Hello Chanchal, Welcome to the world of Python!

Step-by-Step Explanation:

  1. Outer Function (greetFunction):
    - This function takes a parameter language, which is "EN" by default.
    - It has a dictionary greetings storing greetings in different languages.
    - Inside, it defines another function greet(name).
  2. Inner Function (greet):
    - This function uses language from the outer function to select the right greeting.
    - It prints the greeting along with the person’s name.
  3. Returning the Inner Function:
    - The outer function does not execute greet(). Instead, it returns it.
    - When greetFunction() is called, it gives back the greet function.
  4. Using the Function:
    - greetFunction() is called and returns greet.
    - Then, greet("Chanchal") is called, which prints:
Hello Chanchal, Welcome to the world of Python!

Variable Scope:

A variable scope specifies the region where we can access a variable. For example,

def add_numbers():
sum_data = 5 + 4

Here, the sum_data variable is created inside the function, so it can only be accessed within it (local scope). This type of variable is called a local variable.

Based on the scope, we can classify Python variables into three types:

  1. Local Variables
  2. Global Variables
  3. Nonlocal Variables

1. Local Variables:

When we declare variables inside a function, these variables will have a local scope (within the function). We cannot access them outside the function.

These types of variables are called local variables. For example,

def greet():

# local variable
message = 'Hello'

print('Local', message)

greet()

# try to access message variable
# outside greet() function
print(message)


# Output:
# Local Hello
# NameError: name 'message' is not defined

Here, the message variable is local to the greet() function, so it can only be accessed within the function.

hat’s why we get an error when we try to access it outside the greet() function.

To fix this issue, we can make the variable named message global.

2. Global Variables:

In Python, a variable declared outside of the function or in global scope is known as a global variable. This means that a global variable can be accessed inside or outside of the function.

Let’s see an example of how a global variable is created in Python.

# declare global variable
message = 'Hello'

def greet():
# declare local variable
print('Local', message)

greet()
print('Global', message)

# Output:
# Local Hello
# Global Hello

This time we can access the message variable from outside of the greet() function. This is because we have created the message variable as the global variable.

Get Hindi Medium Waala Banda’s stories in your inbox

Join Medium for free to get updates from this writer.

Now, message will be accessible from any scope (region) of the program.

3. Nonlocal Variables:

In Python, the nonlocal keyword is used within nested functions to indicate that a variable is not local to the inner function, but rather belongs to an enclosing function’s scope.

This allows you to modify a variable from the outer function within the nested function, while still keeping it distinct from global variables.

# outside function 
def outer():
message = 'local'

# nested function
def inner():

# declare nonlocal variable
nonlocal message

message = 'nonlocal'
print("inner:", message)

inner()
print("outer:", message)

outer()

# Output:
# inner: nonlocal
# outer: nonlocal

n the above example, there is a nested inner() function. The inner() function is defined in the scope of another function outer().

We have used the nonlocal keyword to modify the message variable from the outer function within the nested function.

Note: If we change the value of a nonlocal variable, the changes appear in the local variable.

Global Keyword

In Python, the global keyword allows us to modify the variable outside of the current scope.

It is used to create a global variable and make changes to the variable in a local context.

First let’s try to access a global variable from the inside of a function,

c = 1 # global variable

def add():
print(c)

add()

# Output: 1

Here, we can see that we have accessed a global variable from the inside of a function.

However, if we try to modify the global variable from inside a function as:

# global variable
c = 1

def add():

# increment c by 2
c = c + 2

print(c)

add()

# Output:
# UnboundLocalError: local variable 'c' referenced before assignment

This is because we can only access the global variable but cannot modify it from inside the function.

The solution for this is to use the global keyword.

# global variable
c = 1

def add():

# use of global keyword
global c

# increment c by 2
c = c + 2

print(c)

add()

# Output: 3

Rules of global Keyword

The basic rules for global keyword in Python are:

  • When we create a variable inside a function, it is local by default.
  • When we define a variable outside of a function, it is global by default. You don’t have to use the global keyword.
  • We use the global keyword to modify (write to) a global variable inside a function.
  • Use of the global keyword outside a function has no effect.

Recursion:

In Python, we know that a function can call other functions. It is even possible for the function to call itself. These types of construct are termed as recursive functions.

The following image shows the working of a recursive function called recurse.

Following is an example of a recursive function to find the factorial of an integer.

Factorial of a number is the product of all the integers from 1 to that number. For example, the factorial of 6 (denoted as 6!) is 1*2*3*4*5*6 = 720

def factorial(x):
"""This is a recursive function
to find the factorial of an integer"""

if x == 1:
return 1
else:
return (x * factorial(x-1))


num = 3
print("The factorial of", num, "is", factorial(num))

Output:

The factorial of 3 is 6

In the above example, factorial() is a recursive function as it calls itself.

When we call this function with a positive integer, it will recursively call itself by decreasing the number.

Each function multiplies the number with the factorial of the number below it until it is equal to one.

Let’s look at an image that shows a step-by-step process of what is going on:

Press enter or click to view image in full size

Our recursion ends when the number reduces to 1. This is called the base condition.

Every recursive function must have a base condition that stops the recursion or else the function calls itself infinitely.

The Python interpreter limits the depths of recursion to help avoid infinite recursions, resulting in stack overflows.

Recursion Limit:

By default, the maximum depth of recursion is 1000. If the limit is crossed, it results in RecursionError.

There is a limit to the depth of possible recursion, which depends on the Python implementation. When the limit is reached, a RuntimeError exception is raised:

def cursing(depth):
try:
cursing(depth + 1) # actually, re-cursing
except RuntimeError as RE:
print('I recursed {} times!'.format(depth))
cursing(0)
# Out: I recursed 1083 times!

It is possible to change the recursion depth limit by using sys.setrecursionlimit(limit) and check this limit by sys.getrecursionlimit().

sys.setrecursionlimit(2000)
cursing(0)
# Out: I recursed 1997 times!

From Python 3.5, the exception is a RecursionError, which is derived from RuntimeError.

Advantages of Recursion:

  1. Recursive functions make the code look clean and elegant.
  2. A complex task can be broken down into simpler sub-problems using recursion.
  3. Sequence generation is easier with recursion than using some nested iteration.

Disadvantages of Recursion:

  1. Sometimes the logic behind recursion is hard to follow through.
  2. Recursive calls are expensive (inefficient) as they take up a lot of memory and time.
  3. Recursive functions are hard to debug.

There are various built-in function in python, we will learn some of here:

1. Lambda Function:

An anonymous, inlined function defined with lambda. The parameters of the lambda are defined to the left of the colon. The function body is defined to the right of the colon. The result of running the function body is (implicitly) returned.

s=lambda x:x*x
s(2)

# OUT: 4

2. Map Function:

Map takes a function and a collection of items. It makes a new, empty collection, runs the function on each item in the original collection and inserts each return value into the new collection. It returns the new collection.

This is a simple map that takes a list of names and returns a list of the lengths of those names:

name_lengths = list(map(len, ["Mary", "Isla", "Sam"]))
print(name_lengths)

# OUT: [4, 4, 3]

Other examples:

square_list = map(lambda x:x*2, [1, 2, 3, 4, 5])
print(square_list)
# Out: [2, 4, 6, 8, 10]
def to_percent(num):
return num * 100

print(list(map(to_percent, [0.95, 0.75, 1.01, 0.1])))
# Out: [95.0, 75.0, 101.0, 10.0]

3. Reduce Function:

Reduce takes a function and a collection of items. It returns a value that is created by combining the items. This is a simple reduce.

It returns the sum of all the items in the collection.

total = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
print(total)
# OUT: 10

4. Filter Function:

Filter takes a function and a collection. It returns a collection of every item for which the function returned True.

arr=[1,2,3,4,5,6]
[i for i in filter(lambda x:x>4,arr)]
# outputs[5,6]

Congratulations, You have finished chapter-4.

--

--