Modular Programming and Modules

Modular Programming

Legos als Module If you want to develop programs which are readable, reliable and maintainable without too much effort, you have use some kind of modular software design. Especially if your application has a certain size. There exists a variety of concepts to design software in modular form. Modular programming is a software design technique to split your code into separate parts. These parts are called modules. The focus for this separation should be to have modules with no or just few dependencies upon other modules. In other words: Minimization of dependencies is the goal. When creating a modular system, several modules are built separately and more or less independently. The executable application will be created by putting them together.

But how do we create modules in Python? A module in Python is just a file containing Python definitions and statements. The module name is moulded out of the file name by removing the suffix .py. For example, if the file name is fibonacci.py, the module name is fibonacci. Let's create a module. We save the following code in the file fibonacci.py:

def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)
def ifib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = b, a + b
    return a
We can import this module in the interactive python shell and call the functions by prefixing them with "fibonacci.":
>>> import fibonacci
>>> fibonacci.fib(30)
832040
>>> fibonacci.ifib(100)
354224848179261915075L
>>> fibonacci.ifib(1000)
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875L
>>>
A local name can be assigned to a module function to get shorter names:
>>> fib = fibonacci.ifib
>>> fib(10)
55
>>>

More on Modules

Usually, modules contain functions, but there can be statements in them as well. These statements can be used to initialize the module. They are only executed when the module is imported.

A module has a private symbol table, which is used as the global symbol table by all functions defined in the module. This is a way to prevent that a global variable of a module accidentally clashes with a user's global variable with the same name. Global variables of a module can be accessed with the same notation as functions, i.e. modname.name
A module can import other modules. It is customary to place all import statements at the beginning of a module or a script.

Importing Names from a Module Directly

Names from a module can directly be imported into the importing module's symbol table:
>>> from fibonacci import fib, ifib
>>> ifib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
This does not introduce the module name from which the imports are taken in the local symbol table. It's possible but not recommended to import all names defined in a module, except those beginning with an underscore "_":
>>> from fibonacci import *
>>> fib(500)
1 1 2 3 5 8 13 21 34 55 89 144 233 377
This shouldn't be done in scripts but it's possible to use it in interactive sessions to save typing.

Important:Each module can only be imported once per interpreter session. If you change a module and if you want to reload it, you must restart the interpreter again. Or you can reload it by using reload(modulename)

Executing Modules as Scripts

Essentially a Python module is a script, so it can be run as a script:
python fibo.py 
The module which has been started as a script will be executed as if it had been imported, but with one exception: The system variable __name__ is set to "__main__". So it's possible to program different behaviour into a module for the two cases. With the following conditional statement the file can be used as a module or as a script, but only if it is run as a script the method fib will be started with a command line argument:
if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))
If it is run as a script, we get the following output:
$ python fibo.py 50
1 1 2 3 5 8 13 21 34 
If it is imported, the code in the if block will not be executed:
>>> import fibo
>>>


Renaming a Namespace

While importing a module, the name of the namespace can be changed:

>>> import math as mathematics
>>> print mathematics.cos(mathematics.pi)
-1.0
After this import there exists a namespace mathematics but no namespace math.
It's possible to import just a few methods from a module:
>>> from math import pi,pow as power, sin as sinus
>>> power(2,3)
8.0
>>> sinus(pi)
1.2246467991473532e-16

Kinds of Modules

There are different kind of modules:

Module Search Path

If you import a module, let's say "import abc", the interpreter searches for this module in the following locations and in the order given:

  1. The directory of the top-level file, i.e. the file being executed.
  2. The directories of PYTHONPATH, if this global variable is set.
  3. standard installation path Linux/Unix e.g. in /usr/lib/python2.5.
It's possible to find out where a module is located after it has been imported:
>>> import math
>>> math.__file__
'/usr/lib/python2.5/lib-dynload/math.so'
>>> import random
>>> random.__file__
'/usr/lib/python2.5/random.pyc'

Content of a Module

With the built-in function dir() and the name of the module as an argument, you can list all valid attributes and methods for that module.

>>> dir(math)
['__doc__', '__file__', '__name__', 'acos', 'asin',
'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 
'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 
'sin', 'sinh', 'sqrt', 'tan', 'tanh']
>>> 
Calling dir() without an argument, a list with the names in the current local scope is returned:
>>> import math
>>> col = ["red","green","blue"]
>>> dir()
['__builtins__', '__doc__', '__name__', 'col', 'math']

It's possible to get a list of the built-in functions:
>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 
...

Module Packages

It's possible to put several modules into a Package. A directory of Python code is said to be a package.
A package is imported like a "normal" module.
Each directory named within the path of a package must also contain a file named __init__.py, or else your package import will fail.