7  Working with modules and packages

As we have seen in Chapter 5 and Chapter 6 it makes sense to pack functionality together that belongs together. In Python this is done via modules and packages.

From the Python glossary we get:

module

An object that serves as an organizational unit of Python code. Modules have a namespace containing arbitrary Python objects. Modules are loaded into Python by the process of importing.

See also package.

package

A Python module which can contain submodules or recursively, subpackages. Technically, a package is a Python module with a path attribute.

See also regular package and namespace package.

Note

We use the same example here as later on in Chapter 13.

We define a class Rectangle with some functions and in addition we include a function is_square in the file but not the class. Everything together it looks like

class Rectangle:
    """A class to represent a rectangle"""
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def get_area(self):
        """Return the area of the rectangle"""
        return self.width * self.height

    def set_width(self, width):
        """Set the width of the rectangle"""
        self.width = width

    def set_height(self, height):
        """Set the height of the rectangle"""
        self.height = height


# Standalone function in the module
def is_square(rectangle: Rectangle):
    """
    Check if a given rectangle is a square.
    
    This function is not part of the Rectangle class, 
    but it operates on a Rectangle object.
    """
    return rectangle.width == rectangle.height

and we store it in the root directory of our project as rectangle.py.

Now, if we call Python we can import the module via its name rectangle (the extension is not used)

import rectangle

rect = rectangle.Rectangle(1, 1)
print(f"{rect.get_area() = }")
print(f"{rectangle.is_square(rect)  = }")
rect.get_area() = 1
rectangle.is_square(rect)  = True

We can also get a specific resource and we can rename it

from rectangle import is_square as check_square

rect = rectangle.Rectangle(1, 1)
print(f"{check_square(rect)  = }")
check_square(rect)  = True

or everything

from rectangle import *

rect = Rectangle(1, 1)
print(f"{is_square(rect)  = }")
is_square(rect)  = True
Note

We can see in the last two examples, that we no longer need to specify the module, i.e. the namespace, to access.

We can also execute the module, which will result in not anything happening for our original file but if we add 1

if __name__ == "__main__":
    import sys
    r = Rectangle(int(sys.argv[1]), int(sys.argv[2]))
    print(f"{r.get_area() = }")

at the end we can call it and we get a result:

python rectangle.py 1 2 
r.get_area() = 2

Now, depending on the input provided the area of the rectangle is computed.

When including or calling a module, Python needs to know where the module is located. This is realised via the module search path. When calling import the following locations are processed:

  1. The build in modules.
  2. The directory containing the input script or file, most of the time this is the current directory
  3. The environment variable PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
  4. The installation-dependent default (by convention including a site-packages directory, handled by the site module), we use pdm to manage these in .venv.

Consequently, if we want to include something in subdirectory we can use src.rectangle where . replaces the path separator /.

We can see this in action when using packages, as we know a collection of modules. For example, numpy or pandas are packages, that contain various modules and we discuss them in the next section.

In order to create a package the __init__.py file needs to be part of a directory, to let Python know that this is a package. As example we have a look at our toy example module again.

$ tree module/
module/
├── pdm.lock
├── pyproject.toml
├── src
1    └── rectangle
2        ├── __init__.py
3        └── rectangle.py
1
The directory containing the module, in our case src.rectangle.
2
The init file.
3
The actual code.

We have two such files where for the module part the content is.

from .rectangle import *

This explicitly uses the current directory and imports everything into the namespace of the module. Therefore, we do not need to write rectangle.rectangle.Rectangle to use the Rectangle class but just rectangle.Rectangle.

This concludes our short introduction to modules, for further details we suggest the following references.

7.1 References

We only provide a glimpse into the concept of functional programming in Python. There are more guides out there, like

  • Matthes (2023) has a section about modules.
  • The Python Tutorial on Modules and Packages, link
  • A RealPython guide on modules and packages, link

  1. The magic of __name__ == "__main__" is explained in the docs↩︎