Modules, packages and imports#

Education objectives

  • modules and packages

  • imports: import, from, *

  • isort, ruff

  • module and package structure

Motivations: import code from other files#

Useful for:

  • reusing code

  • organizing code

  • distributing code (not detailed here)

Definitions: modules and packages

  • A module is a python file that can be imported.

  • A package is a directory containing module(s).

Multi-file program and imports#

Simple case of a directory with 2 files#

Let’s study this very simple “package” (i.e. a directory) containing 2 files:

import os

os.listdir("../common/examples/example0")
['prog.py', 'util.py']

The file util.py contains:

print("begin of util.py")
myvar0 = 0
myvar1 = 1


def print_variables():
    print(f"in function print_variables: {myvar0 = }; {myvar1 = }")

Let’s run it:

%run ../common/examples/example0/util.py
begin of util.py

The file prog.py contains:

print("in prog.py (before imports)")

# 2 different syntaxes for importing a module
import util
from util import myvar1, print_variables

util.myvar0 = 100
myvar1 += 100
print(f"in prog.py (after imports), {util.myvar0 = }; {myvar1 = }")
print_variables()

Let’s run it:

%run ../common/examples/example0/prog.py
in prog.py (before imports)
begin of util.py
in prog.py (after imports), util.myvar0 = 100; myvar1 = 101
in function print_variables: myvar0 = 100; myvar1 = 1

Note

Files imported more than once are executed only once per process.

More advanced notions#

In the directory example1, we also have 2 files:

os.listdir("../common/examples/example1")
['prog.py', 'util.py']

The files are just slightly modified versions of the files in ../common/examples/example0. The goal is to try to understand:

  • the very common idiom if __name__ == "__main__": ....

  • where the Python interpreters looks for module and the attribute sys.path.

../common/examples/example1/util.py contains:

print("begin of util.py")
myvar0 = 0
myvar1 = 1


def print_variables():
    print(f"in function print_variables: {myvar0 = }; {myvar1 = }")


print(f"in util.py, {__name__ =}")
# __name__ is a special variable always defined in a Python file.
# its value depends on how the file is called (directly executed or imported)
if __name__ == "__main__":
    # this code is executed only in the file is directly executed
    print("the module util.py has been directly executed")
    print_variables()
    print("end of util.py")
else:
    # usually, we do nothing particular for this case (file imported)
    print("the module util.py has been imported")

Let’s study what it gives when one executes it directly:

%run ../common/examples/example1/util.py
begin of util.py
in util.py, __name__ ='__main__'
the module util.py has been directly executed
in function print_variables: myvar0 = 0; myvar1 = 1
end of util.py

And ../common/examples/example1/prog.py contains:

print("in prog.py (before imports)")

import sys
from pprint import pprint

# 2 different syntaxes for importing a module
import util
from util import myvar1, print_variables

print("sys.path[:4]:")
pprint(sys.path[:4])

util.myvar0 = 100
myvar1 += 100
print(f"in prog.py (after imports), {util.myvar0 = }; {myvar1 = }")
print_variables()

Let’s run it:

%run ../common/examples/example1/prog.py
in prog.py (before imports)
sys.path[:4]:
['/builds/py-edu-fr/py-edu-fr/src/common/examples/example1',
 '/usr/local/lib/python313.zip',
 '/usr/local/lib/python3.13',
 '/usr/local/lib/python3.13/lib-dynload']
in prog.py (after imports), util.myvar0 = 100; myvar1 = 101
in function print_variables: myvar0 = 100; myvar1 = 1

sys.path

From https://docs.python.org/3/library/sys.html:

A list of strings that specifies the search path for modules. Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.

As initialized upon program startup, the first item of this list, path[0], is the directory containing the script that was used to invoke the Python interpreter.

Warning about syntax from ... import *#

There is another import syntax, with a star:

from matplotlib.pylab import *

It imports in the global namespace all names of the namespace matplotlib.pylab. It can be useful in some situations but should be avoid in many cases. With this syntax, you don’t know from where come the names and automatic code analysis becomes much more difficult.

Standard structure of a Python module#

"""A program...

Documentation of the module.

"""

# import functions, modules and/or classes

from math import sqrt

# definition of functions and/or classes


def mysum(variables):
    """sum all the variables of the function and return it.
    No type check
    :param variables: (iterable) an iterable over elements
                      that can be summed up
    :return: the sum of the variables
    """
    result = 0
    for var in variables:
        result += var
    return result


def main():
    l = [1, 2, 3, 4]
    print("the square of mysum(l) is", sqrt(mysum(l)))


# main part of the program (protected)
if __name__ == "__main__":
    main()
the square of mysum(l) is 3.1622776601683795

Note

Here, we use a main() function. The name main is purely conventional—you could use any function name.

Using such a function avoids placing too much code (and variables) at the global level of the module. This is beneficial because accessing local variables is significantly faster than accessing global variables.

However, during development, it’s often convenient to write code at the global level. This allows global variables to be directly accessible in IPython after a %run magic command, which can facilitate debugging.