Functions & Modules#

Introduction#


In Python, a function is a block of organized, reusable (DRY- Don’t Repeat Yourself) code with a name that is used to perform a single, specific task. It can take arguments and returns the value.

Functions help break our program into smaller and modular chunks.

  1. Built-in functions

    The functions which are come along with Python itself.

    Examples: range(), print(), input(), type(), id(), eval() etc.

  2. User-defined function

    Functions which are created by programmer explicitly according to the requirement

Syntax:

def function_name(parameter1, parameter2):
    """docstring"""
    # function body    
    # write some action
return value
function

Functions without parameters and return value#

# Example 1: 
    
def greet():
    print("Welcome to Python for Data Science")

# call function using its name
greet()
Welcome to Python for Data Science

Functions with return value#

# Example 2: 

def generate_full_name ():
    first_name = 'Anukool'
    last_name = 'Chaturvedi'
    space = ' '
    full_name = first_name + space + last_name
    return full_name
print(generate_full_name())

Functions with parameters#

# Example 1:  with 1 parameter and no return value

def sum_of_numbers(n):
    total = 0
    for i in range(n+1):
        total+=i
    print(total)
print(sum_of_numbers(10))  # 55
print(sum_of_numbers(100)) # 5050
55
None
5050
None
# Example 2:  With multiple parameters & return values

def sum_two_numbers (num_one, num_two):  # two parameter
    sum = num_one + num_two
    return sum
print('Sum of two numbers: ', sum_two_numbers(1, 9))
Sum of two numbers:  10

If we do not return a value with a function, then our function is returning None by default.

Returning multiple values#

In Python you can also return multiple values from a function. Use the return statement by separating each expression by a comma.

# Example 1:

def arithmetic(num1, num2):
    add = num1 + num2
    sub = num1 - num2
    multiply = num1 * num2
    division = num1 / num2
    # return four values
    return add, sub, multiply, division

a, b, c, d = arithmetic(10, 2)  # read four return values in four variables

print("Addition: ", a)
print("Subtraction: ", b)
print("Multiplication: ", c)
print("Division: ", d)
Addition:  12
Subtraction:  8
Multiplication:  20
Division:  5.0

Passing default values#

You can also pass default values to a function. If the value is not provided, the default value will be used

# Example: 

def calculate_age (birth_year,current_year = 2021):
    age = current_year - birth_year
    return age;
print('Age: ', calculate_age(1821))
Age:  200

Docstrings#

docstring is short for documentation string. It is a descriptive text (like a comment) written by a programmer to let others know what block of code does.

Although optional, documentation is a good programming practice.

It is being declared using triple single quotes ''' ''' or triple-double quote """ """ so that docstring can extend up to multiple lines.

We can access docstring using doc attribute __doc__ for any object like list, tuple, dict, and user-defined function, etc.

# Example

def swap(x, y):
    # Docstring
    """
    This function swaps the value of two variables
    """
    temp = x;  # value of x will go inside temp
    x = y;     # value of y will go inside x
    y = temp;  # value of temp will go inside y
    print("value of x is:", x)
    print("value of y is:", y)
    return     # "return" is optional

x = 6
y = 9
swap(x, y)     #call function
value of x is: 9
value of y is: 6
print(swap.__doc__)
    This function swaps the value of two variables
    

Variable scope#


When we define a function with variables, then those variables scope is limited to that function.

In Python, the scope of a variable is the portion of a program where the variable is declared.

Parameters and variables defined inside a function are not visible from outside the function. Hence, it is called the variable’s local scope.

Global#

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.

# Example

x = "global"

def fun():
    print("x inside:", x)

fun()
print("x outside:", x)
x inside: global
x outside: global

We can’t change the value of a global variable inside a function.

# Example

x = "global"

def fun():
    x = x * 2
    print(x)

fun()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
Cell In[15], line 9
      6     x = x * 2
      7     print(x)
----> 9 fun()

Cell In[15], line 6, in fun()
      5 def fun():
----> 6     x = x * 2
      7     print(x)

UnboundLocalError: cannot access local variable 'x' where it is not associated with a value

To make this work, we can use global keyword. global keyword allows you 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.

# Modify a global variable inside a function

x = "global"

def fun():
    global x
    x = x * 2
    print(x)

fun()
globalglobal

Local#

A variable declared inside the function’s body or in the local scope is known as a local variable.

# Example: fun2 can't access loc_var defined in fun1, as it is local to fun1 

def fun1():
    loc_var = 999   # local variable
    print("Value is :", loc_var)

def fun2():
    print("Value is :", loc_var)

fun1()
fun2()
Value is : 999
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[19], line 11
      8     print("Value is :", loc_var)
     10 fun1()
---> 11 fun2()

Cell In[19], line 8, in fun2()
      7 def fun2():
----> 8     print("Value is :", loc_var)

NameError: name 'loc_var' is not defined

Aruguments#


In Python, there are other ways to define a function that can take variable number of arguments.

Three different forms of this type are described below:

  1. Default Arguments

  2. Keyword Arguments

  3. Arbitrary/Variable-length Arguments

Default Arguments#

The default argument is a parameter that has a default value. If the value is not provided, the default value will be used.

# Example:

def greet(name, msg="Good morning!"):  # two arguments: `name` is fixed arg and 'msg' is variable arg

    print("Hello", name + ', ' + msg)


greet("Alan")
greet("Bruce", "How do you do?")
greet("Carson","Good night!")
Hello Alan, Good morning!
Hello Bruce, How do you do?
Hello Carson, Good night!

Keyword Arguments#

A keyword argument is an argument value, passed to function preceded by the variable name and an equals sign.

This allows you to skip arguments or place them out of order because the Python interpreter is able to use the keywords provided to match the values with parameters.

# Example 1:

greet("Eric", "How do you do?")

# 2 keyword arguments
greet(name = "Eric",msg = "How do you do?")

# 2 keyword arguments (out of order)
greet(msg = "How do you do?",name = "Eric") 

# 1 positional, 1 keyword argument
greet("Eric", msg = "How do you do?")           
Hello Eric, How do you do?
Hello Eric, How do you do?
Hello Eric, How do you do?
Hello Eric, How do you do?

As we can see, we can mix positional arguments with keyword arguments during a function call. But we must keep in mind that keyword arguments must follow positional arguments.

  1. Having a positional argument after keyword arguments will result in errors.

  2. In keyword arguments, order of argument doesn’t not matter, but the number of arguments must match. Otherwise, we will get an error.

greet(name="Eric","How do you do?")  # Will result in an error
  Cell In[22], line 1
    greet(name="Eric","How do you do?")  # Will result in an error
                                      ^
SyntaxError: positional argument follows keyword argument

Arbitrary/Variable-length Arguments#

Sometimes, we do not know in advance the number of arguments that will be passed into a function. Python allows us to handle this kind of situation through function calls with an arbitrary number of arguments.

We can pass any number of arguments to this function. Internally all these values are represented in the form of a tuple.

In the function definition, we use an asterisk * before the parameter name to denote this kind of argument. For example:

# Example 1: 

def greet(*names):
    """This function greets all the person in the names tuple."""
    # names is a tuple with arguments
    for name in names:
        print("Hello", name)

greet("Gary", "Hank", "Ivan", "John")
Hello Gary
Hello Hank
Hello Ivan
Hello John

Function as a Parameter#

In Python, we can pass a function as a parameter to another function.

# Example 1: You can pass functions around as parameters

def square_number (n):
    return n * n
def do_something(f, x):
    return f(x)
print(do_something(square_number, 3)) # 27
9

Recursion#


Recursion is the process of defining something in terms of itself.

A physical world example would be to place two parallel mirrors facing each other. Any object in between them would be reflected recursively.

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.

../../_images/re1.png
# Example 1:

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

    if n == 1:
        return 1
    else:
        return (n * factorial(n-1))  # 3 * 2 * 1 = 6


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

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. This recursive call can be explained in the following steps.

factorial(3)          # 1st call with 3
3 * factorial(2)      # 2nd call with 2
3 * 2 * factorial(1)  # 3rd call with 1
3 * 2 * 1             # return from 3rd call as number=1
3 * 2                 # return from 2nd call
6                     # return from 1st call

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

recursion2

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.

Anonymous / Lambda Functions#


In Python, an anonymous function is a function that is defined without a name.

While normal functions are defined using the def keyword in Python, anonymous functions are defined using the lambda keyword.

For example: lambda n:n+n

Anonymous functions make the code very concise and easy to read

Hence, anonymous functions are also called lambda functions.

  • Lambda forms can take any number of arguments but return just one value in the form of an expression. They cannot contain commands or multiple expressions.

  • An anonymous function cannot be a direct call to print because lambda requires an expression.

  • lambda functions have their own local namespace and cannot access variables other than those in their parameter list and those in the global namespace.

  • Although it appears that lambdas are a one-line version of a function, they are not equivalent to inline statements in C or C++, whose purpose is to stack allocation by passing function, during invocation for performance reasons.

Syntax:

lambda argument_list: expression
# Example 1: Program for even numbers without lambda function

def even_numbers(nums):
    even_list = []
    for n in nums:
        if n % 2 == 0:
            even_list.append(n)
    return even_list

num_list = [10, 9, 16, 78, 2, 3, 7, 1]
ans = even_numbers(num_list)
print("Even numbers are:", ans)
Even numbers are: [10, 16, 78, 2]
# Example 1: Program for even number with a lambda function

l = [10, 9, 16, 78, 2, 3, 7, 1]
even_nos = list(filter(lambda x: x % 2 == 0, l))
print("Even numbers are: ", even_nos)
Even numbers are:  [10, 16, 78, 2]

x is the argument of the function, x % 2 is the expression

Modules#


Module containing a set of codes or a set of functions which can be included to an application.

Modules refer to the Python file, which contains Python code like Python statements, classes, functions, variables, etc. A file with Python code is defined with extension .py

For example: In main.py, where the main is the module name.

In Python, large code is divided into small modules. The benefit of modules is, it provides a way to share reusable functions.

Advantage

  • Reusability: Module can be used in some other python code. Hence it provides the facility of code reusability.

  • Categorization: Similar type of attributes can be placed in one module.

Creating a module#

A file containing Python code, for example: example.py, is called a module, and its module name would be example.

Let us create a module. Type the following and save it as example.py.

# Python Module example

>>> def add(a, b):
>>>     """This program adds two numbers and return the result"""
>>>     result = a + b
>>>     return result

Importing a module#

We can import the definitions inside a module to another module or the interactive interpreter in Python.

👉 We use the import keyword to do this.

# Importing a module
>>> import example
>>> example.add(2, 3)

👉 You can also import specific objects from a module. For example:

>>> from example import add
>>> add(2, 3)

👉 You can access submodules and import specific objects using the dot operator. For example:

>>> from example.submodule import add
>>> add(2, 3)

👉 You can also import all objects from a module. For example:

>>> from example import *
>>> add(2, 3)

👉 You can import multiple objects from a module. For example:

>>> from example import add, sub
>>> add(2, 3)

👉 You can give a new name or alias to the imported objects.

>>> import example as e
>>> e.add(2, 3)

Module path#

While importing a module, Python looks at several places. Interpreter first looks for a built-in module. Then(if built-in module not found), Python looks into a list of directories defined in sys.path. The search is in this order.

  1. The current directory.

  2. PYTHONPATH (an environment variable with a list of directories).

  3. The installation-dependent default directory.

>>>import sys
>>>sys.path

Built-in modules#

Built-in modules are pre-installed with Python. You can see all built-in modules with the help('modules') method

help('modules')
Please wait a moment while I gather a list of all available modules...
/opt/homebrew/Caskroom/miniconda/base/envs/ml-notes/lib/python3.12/pkgutil.py:78: UserWarning: The numpy.array_api submodule is still experimental. See NEP 47.
  __import__(info.name)
IPython             aifc                ipykernel_launcher  sched
PIL                 alabaster           itertools           secrets
__future__          antigravity         jedi                select
__hello__           appnope             jinja2              selectors
__phello__          argparse            json                setuptools
_abc                array               jsonschema          shelve
_aix_support        ast                 jsonschema_specifications shlex
_ast                asttokens           jupyter             shutil
_asyncio            asyncio             jupyter_book        signal
_bisect             atexit              jupyter_cache       site
_blake2             attr                jupyter_client      six
_bz2                attrs               jupyter_core        smtplib
_codecs             audioop             keyword             sndhdr
_codecs_cn          babel               kiwisolver          snowballstemmer
_codecs_hk          base64              latexcodec          socket
_codecs_iso2022     bdb                 lib2to3             socketserver
_codecs_jp          binascii            linecache           soupsieve
_codecs_kr          bisect              linkify_it          sphinx
_codecs_tw          bs4                 locale              sphinx_book_theme
_collections        builtins            logging             sphinx_comments
_collections_abc    bz2                 lzma                sphinx_copybutton
_compat_pickle      cProfile            mailbox             sphinx_design
_compression        calendar            mailcap             sphinx_external_toc
_contextvars        certifi             markdown_it         sphinx_jupyterbook_latex
_crypt              cgi                 markupsafe          sphinx_multitoc_numbering
_csv                cgitb               marshal             sphinx_thebe
_ctypes             charset_normalizer  math                sphinx_togglebutton
_ctypes_test        chunk               matplotlib          sqlalchemy
_curses             click               matplotlib_inline   sqlite3
_curses_panel       cmath               mdit_py_plugins     sre_compile
_datetime           cmd                 mdurl               sre_constants
_dbm                code                mimetypes           sre_parse
_decimal            codecs              mmap                ssl
_distutils_hack     codeop              modulefinder        stack_data
_elementtree        collections         multiprocessing     stat
_functools          colorsys            myst_nb             statistics
_hashlib            comm                myst_parser         string
_heapq              compileall          nbclient            stringprep
_imp                concurrent          nbformat            struct
_io                 configparser        nest_asyncio        subprocess
_json               contextlib          netrc               sunau
_locale             contextvars         nis                 symtable
_lsprof             contourpy           nntplib             sys
_lzma               copy                ntpath              sysconfig
_markupbase         copyreg             nturl2path          syslog
_md5                crypt               numbers             tabnanny
_multibytecodec     csv                 numpy               tabulate
_multiprocessing    ctypes              opcode              tarfile
_opcode             curses              operator            telnetlib
_operator           cycler              optparse            tempfile
_osx_support        dataclasses         os                  termios
_pickle             datetime            packaging           test
_posixshmem         dateutil            pandas              tests
_posixsubprocess    dbm                 parso               textwrap
_py_abc             debugpy             pathlib             this
_pydatetime         decimal             pdb                 threading
_pydecimal          decorator           pexpect             time
_pyio               difflib             pickle              timeit
_pylong             dis                 pickleshare         tkinter
_queue              doctest             pickletools         token
_random             docutils            pip                 tokenize
_scproxy            email               pipes               tomllib
_sha1               encodings           pkg_resources       tornado
_sha2               ensurepip           pkgutil             trace
_sha3               enum                platform            traceback
_signal             errno               platformdirs        tracemalloc
_sitebuiltins       exceptiongroup      plistlib            traitlets
_socket             executing           poplib              tty
_sqlite3            fastjsonschema      posix               turtle
_sre                faulthandler        posixpath           turtledemo
_ssl                fcntl               pprint              types
_stat               filecmp             profile             typing
_statistics         fileinput           prompt_toolkit      typing_extensions
_string             fnmatch             pstats              tzdata
_strptime           fontTools           psutil              uc_micro
_struct             fractions           pty                 unicodedata
_symtable           ftplib              ptyprocess          unittest
_sysconfigdata__darwin_darwin functools           pure_eval           urllib
_sysconfigdata_arm64_apple_darwin20_0_0 gc                  pwd                 urllib3
_testbuffer         genericpath         py_compile          uu
_testcapi           getopt              pybtex              uuid
_testclinic         getpass             pybtex_docutils     venv
_testimportmultiple gettext             pyclbr              warnings
_testinternalcapi   glob                pydata_sphinx_theme wave
_testmultiphase     graphlib            pydoc               wcwidth
_testsinglephase    grp                 pydoc_data          weakref
_thread             gzip                pyexpat             webbrowser
_threading_local    hashlib             pygments            wheel
_tkinter            heapq               pylab               wsgiref
_tokenize           hmac                pyparsing           xdrlib
_tracemalloc        html                pytz                xml
_typing             http                queue               xmlrpc
_uuid               idlelib             quopri              xxlimited
_warnings           idna                random              xxlimited_35
_weakref            imagesize           re                  xxsubtype
_weakrefset         imaplib             readline            yaml
_xxinterpchannels   imghdr              referencing         zipapp
_xxsubinterpreters  importlib           reprlib             zipfile
_xxtestfuzz         importlib_metadata  requests            zipimport
_yaml               inspect             resource            zipp
_zoneinfo           io                  rlcompleter         zlib
a11y_pygments       ipaddress           rpds                zmq
abc                 ipykernel           runpy               zoneinfo

Enter any module name to get more help.  Or, type "modules spam" to search
for modules whose name or summary contain the string "spam".

dir()#

We can use the dir() function to find out names that are defined inside a module

import sys
dir(sys)
['__breakpointhook__',
 '__displayhook__',
 '__doc__',
 '__excepthook__',
 '__interactivehook__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '__stderr__',
 '__stdin__',
 '__stdout__',
 '__unraisablehook__',
 '_base_executable',
 '_clear_type_cache',
 '_current_exceptions',
 '_current_frames',
 '_debugmallocstats',
 '_framework',
 '_getframe',
 '_getframemodulename',
 '_git',
 '_home',
 '_setprofileallthreads',
 '_settraceallthreads',
 '_stdlib_dir',
 '_xoptions',
 'abiflags',
 'activate_stack_trampoline',
 'addaudithook',
 'api_version',
 'argv',
 'audit',
 'base_exec_prefix',
 'base_prefix',
 'breakpointhook',
 'builtin_module_names',
 'byteorder',
 'call_tracing',
 'copyright',
 'deactivate_stack_trampoline',
 'displayhook',
 'dont_write_bytecode',
 'exc_info',
 'excepthook',
 'exception',
 'exec_prefix',
 'executable',
 'exit',
 'flags',
 'float_info',
 'float_repr_style',
 'get_asyncgen_hooks',
 'get_coroutine_origin_tracking_depth',
 'get_int_max_str_digits',
 'getallocatedblocks',
 'getdefaultencoding',
 'getdlopenflags',
 'getfilesystemencodeerrors',
 'getfilesystemencoding',
 'getprofile',
 'getrecursionlimit',
 'getrefcount',
 'getsizeof',
 'getswitchinterval',
 'gettrace',
 'getunicodeinternedsize',
 'hash_info',
 'hexversion',
 'implementation',
 'int_info',
 'intern',
 'is_finalizing',
 'is_stack_trampoline_active',
 'last_traceback',
 'last_type',
 'last_value',
 'maxsize',
 'maxunicode',
 'meta_path',
 'modules',
 'monitoring',
 'orig_argv',
 'path',
 'path_hooks',
 'path_importer_cache',
 'platform',
 'platlibdir',
 'prefix',
 'ps1',
 'ps2',
 'ps3',
 'pycache_prefix',
 'set_asyncgen_hooks',
 'set_coroutine_origin_tracking_depth',
 'set_int_max_str_digits',
 'setdlopenflags',
 'setprofile',
 'setrecursionlimit',
 'setswitchinterval',
 'settrace',
 'stderr',
 'stdin',
 'stdlib_module_names',
 'stdout',
 'thread_info',
 'unraisablehook',
 'version',
 'version_info',
 'warnoptions']

os()#

Used to automatically perform many operating system tasks. It provides functions for creating, changing current working directory, and removing a directory (folder), fetching its contents, changing and identifying the current directory.

# Example 1:

# import the module
import os
# Getting current working directory
os.getcwd()

sys()#

Used to access system specific parameters and functions. It provides access to some variables used or maintained by the interpreter and to functions that interact strongly with the interpreter.

import sys
sys.path

string()#

A string module is a useful module for working with text. It contains a set of functions that are used to perform certain operations on characters. This module has pre-defined functions like capwords, ascii_letters, etc.

# Example 1:

from string import *
print(capwords("hello world")) #capitalizes the first letter of each words
print(ascii_letters) #prints all lowercase and uppercase letters
Hello World
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

random()#

The random module is used to generate the random numbers. It provides the following two built in functions:

Function

Description

random()

It returns a random number between 0.0 and 1.0 where 1.0 is exclusive.

randint(x,y)

It returns a random number between x and y where both the numbers are inclusive.

# Example 1:

from random import *
print(randint(10, 20)) #prints a random number between the given range

list1 = [30, 23, 45, 16, 89, 56]
print(choice(list1)) #prints a random element from the given iterator

print(uniform(10, 20)) #prints a random float number between two given values
10
30
14.78589902230161

math()#

The math module is used to perform mathematical operations. It provides some pre-defined mathematical functions like sqrt, factorial, etc.

Function

Description

ceil(n)

It returns the next integer number of the given number.

sqrt(n)

It returns the Square root of the given number.

exp(n)

It returns the natural logarithm e raised to the given number.

floor(n)

It returns the previous integer number of the given number.

log(n,baseto)

It returns the previous integer number of the given number.

pow(baseto, exp)

It returns baseto raised to the exp power.

sin(n)

It returns sine of the given radian.

cos(n)

It returns cosine of the given radian.

tan(n)

It returns tangent of the given radian.

from math import *
print(sqrt(16)) # prints the square root of the value 16 in the form of a floating-point value
print(factorial(5)) # prints the factorial of the value 5
4.0
120

There are alot more builtin and user defined modules, you can read more about them here

Packages#

We want to divide our code base into clean, efficient modules using Python packages.

We don’t usually store all of our files on our computer in the same location. We use a well-organized hierarchy of directories for easier access.

Similar files are kept in the same directory, for example, we may keep all the songs in the “music” directory. Analogous to this, Python has packages for directories and modules for files.

As our application program grows larger in size with a lot of modules, we place similar modules in one package and different modules in different packages. This makes a project (program) easy to manage and conceptually clear.

Similarly, as a directory can contain subdirectories and files, a Python package can have sub-packages and modules.

A directory must contain a file named __init__.py in order for Python to consider it as a package. This file can be left empty but we generally place the initialization code for that package in this file.

Here is an example. Suppose we are developing a game. One possible organization of packages and modules could be as shown in the figure below.

../../_images/pack1.png

Package Module Structure in Python Programming

We can import modules from packages using the dot . operator.

For example, if we want to import the start module in the above example, it can be done as follows:

>>>import Game.Level.start

Now, if this module contains a function named select_difficulty(), we must use the full name to reference it.

>>>Game.Level.start.select_difficulty(2)

If this construct seems lengthy, we can import the module without the package prefix as follows:

>>>from Game.Level import start

We can now call the function simply as follows:

>>>start.select_difficulty(2)

Another way of importing just the required function (or class or variable) from a module within a package would be as follows:

>>>from Game.Level.start import select_difficulty

Now we can directly call this function.

>>>select_difficulty(2)

Although easier, this method is not recommended. Using the full namespace avoids confusion and prevents two same identifier names from colliding.

While importing packages, Python looks in the list of directories defined in sys.path, similar as for module search path.