Modules

Modules are used in Python to organise larger projects. The Python standard library is divided into modules to make it more manageable. You don’t have to organise your own code into modules, but if you write larger programs or code that you want to reuse, you should do so.

What is a module?

A module is a file that contains code. It defines a group of Python functions or other objects, and the name of the module is derived from the name of the file.

Modules usually contain Python source code [1], group related Python objects together and help to avoid naming conflicts. You can write a module called mymodule for your programme that defines a function called my_func. In the same programme, you may also want to use another module called othermodule, which also defines a function called my_func, but does something different from your my_func function. Without modules, it would be impossible to use two different functions with the same name. With modules, you can refer to the functions mymodule.my_func and othermodule.my_func in your main programme. Using the module names ensures that the two my_func functions are not confused, as Python uses so-called Namespaces. A namespace is essentially a dictionary of names for the functions, classes, modules, etc. available there.

Namespaces are one honking great idea – let’s do more of those!

– The Zen of Python, by Tim Peters

>>> import cmath, math
>>> math.sin(34)
0.5290826861200238
>>> cmath.sin(34)
(0.5290826861200238-0j)

Warning

However, an import using wildcards negates this separation of namespaces: import * carries over every name from one module to another.

Modules are also used to make Python itself more manageable. Most of Python’s standard functions are not integrated into the core of the language, but are provided via special modules that you can load as needed.

Creating modules

Probably the best way to learn about modules is to create your own module. To do this, we create a text file called wc.py, and enter the Python code below into this text file. If you use IDLE, select File ‣ New Window and start typing.

It is easy to create your own modules that can be imported and used in the same way as Python’s built-in library modules. The following example is a simple module with a function that prompts for a file name and determines the number of words in this file.

 1"""wc module. Contains function: words_occur()"""
 2
 3
 4def words_occur():
 5    """words_occur() - count the occurrences of words in a file."""
 6    # Prompt user for the name of the file to use.
 7    file_name = input("Enter the name of the file: ")
 8    # Open the file, read it and store its words in a list.
 9    with open(file_name, "r") as f:
10        word_list = f.read().split()
11    # Count the number of occurrences of each word in the file.
12    occurs_dict = {}
13    for word in word_list:
14        # increment the occurrences count for this word
15        occurs_dict[word] = occurs_dict.get(word, 0) + 1
16    # Print out the results.
17    print(
18        f"File {file_name} has {len(word_list)} words, "
19        f"{len(occurs_dict)} are unique:"
20    )
21    print(occurs_dict)
22
23
24if __name__ == "__main__":
25    words_occur()
Lines 1 and 5

Docstrings are standard methods for documenting modules, functions, methods and classes.

Line 10

read returns a string containing all the characters in a file, and split returns a list of the words in a string using spaces.

Lines 25 to 26

With this if-statement you can use the programme in two ways:

  • for importing in the Python shell or another Python script __name__ is the filename:

    >>> import wc
    >>> wc.words_occur()
    Enter the name of the file: README.rst
    File README.rst has 350 words (187 are unique)
    {'Quick': 1, ...}
    

    Alternatively, you can also import words_occur directly:

    >>> from wc import words_occur
    >>> words_occur()
    Enter the name of the file: README.rst
    File README.rst has 350 words (187 are unique)
    {'Quick': 1, ...}
    

    You can use the interactive mode of the Python shell or IDLE to incrementally test a module as you create it. However, if you change your module on disk, entering the import command again will not reload it. For this purpose, you must use the reload function from the importlib module:

    >>> import wc, importlib
    >>> importlib.reload(wc)
    <module 'wc' from '/home/veit/.local/lib/python3.14/site-packages/wc.py'>
    
  • as a script it is executed with the name __main__ and the function words_occur()`` is called:

    $ python3 wc.py
    Enter the name of the file: README.rst
    File README.rst has 350 words (187 are unique)
    {'Quick': 1, ...}
    

First save this code in one of the directories of the module search path, which can be found in the list of sys.path. We recommend .py as the file name extension, as this identifies the file as Python source code.

Note

The list of directories displayed with sys.path depends on your system configuration. This list of directories is searched by Python in the order when an import statement is executed. The first module found that matches the import request is used. If there is no matching module in this search path, an ImportError is raised.

If you are using IDLE, you can view the search path and the modules it contains graphically by using the File ‣ Path Browser window.

The variable sys.path is initialised with the value of the environment variable PYTHONPATH, if it exists. When you run a Python script, the sys.path variable for that script will have the directory where the script is located as the first element, so you can conveniently find out where the executing Python programme is located.

Command line arguments

In our example, if you want to pass the file name as a command line argument, for example

$ python3 wc.py README.rst

you can easily do this with the following modification of our script:

--- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wc.py
+++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargv.py
@@ -1,10 +1,12 @@
 """wc module. Contains function: words_occur()"""
+
+import sys
 
 
 def words_occur():
     """words_occur() - count the occurrences of words in a file."""
     # Prompt user for the name of the file to use.
-    file_name = input("Enter the name of the file: ")
+    file_name = sys.argv.pop()
     # Open the file, read it and store its words in a list.
     with open(file_name, "r") as f:
         word_list = f.read().split()
sys.argv

returns a list of command line arguments passed to a Python script. argv[0] is the script name.

.pop

removes the element at the given position in the list and returns it. If no index is specified, .pop() removes the last element in the list and returns it.

The argparse module

You can configure a script to accept command line options as well as arguments. The argparse module supports parsing of different argument types and can even generate messages. To use the argparse module, create an instance of ArgumentParser, fill it with arguments, and then read both the optional and positional arguments. The following example illustrates the use of the module:

--- /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wc.py
+++ /home/runner/work/python-basics-tutorial/python-basics-tutorial/docs/modules/wcargparse.py
@@ -1,10 +1,15 @@
 """wc module. Contains function: words_occur()"""
+
+from argparse import ArgumentParser
 
 
 def words_occur():
     """words_occur() - count the occurrences of words in a file."""
+    parser = ArgumentParser()
     # Prompt user for the name of the file to use.
-    file_name = input("Enter the name of the file: ")
+    parser.add_argument("-f", "--file", dest="filename", help="read data from the file")
+    args = parser.parse_args()
+    file_name = args.filename
     # Open the file, read it and store its words in a list.
     with open(file_name, "r") as f:
         word_list = f.read().split()

This code creates an instance of ArgumentParser and then adds the filename argument. The argparse module returns a namespace object that contains the arguments as attributes. You can retrieve the values of the arguments with dot notation, in our case with args.filename.

You can now call the script with:

$ python3 wcargparse.py -f index.rst

In addition, a help option -h or --help is automatically generated:

$ python3 wcargparse.py -h
usage: wcargparse.py [-h] [-f FILENAME]

optional arguments:
  -h, --help            show this help message and exit
  -f FILENAME, --file FILENAME
                        read data from the file

Declaring a public API with __all__

You can use __all__ to expose a public API for your module, which determines what is imported when you use from MODULE import *. Let’s assume your module looks like this:

perimeter.py
from math import sqrt

__all__ = ["circle_perimeter", "square_length", "square_perimeter"]
pi = 3.141592653589793


def circle_perimeter(diameter):
    return diameter * pi


def square_perimeter(length):
    return length * 4


def square_length(area):
    return sqrt(area)

You can then use this module as follows:

>>> from perimeter import *
>>> circle_perimeter(3)
9.42477796076938
>>> square_perimeter(3)
12
>>> square_length(16)
4.0

However, you cannot use the pi variable from the module:

>>> pi
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    pi
NameError: name 'pi' is not defined

Even with dir(), you’ll still only get those two functions from __all__:

>>> dir()
['__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'circle_perimeter', 'square_length', 'square_perimeter']

The advantage of this is that internal helper functions and variables are encapsulated and are not accidentally exported. Without __all__, any name not beginning with an underscore would also have been imported, including pi and the sqrt function, which we imported from math.

The help() function also reads from __all__ and does not document everything in the module; the internal variable pi and sqrt, which does not even originate from us, are not displayed:

>>> import perimeter
>>> help(perimeter)
Help on module perimeter:

NAME
    perimeter

FUNCTIONS
    circle_perimeter(diameter)

    square_length(area)

    square_perimeter(length)

DATA
    __all__ = ['circle_perimeter', 'square_length', 'square_perimeter']

Checks

  • If you have created a my_math module that contains a divide() function, what options are there for importing this function and then using it? What are the advantages and disadvantages of each option?

  • A variable min is contained in the scope.py module. In which of the following contexts can min be used?

    1. With the module itself

    2. Within the scope() function of the module

    3. Within a script that has imported the scope.py module

  • Pack the functions that you created at the end of Decorators as an independent module. The functions should initially only be fully usable from another script.

  • Make your module executable.