From module to package#

In the previous part, we saw how to create modular code that can be reused. The next level of reusability is to create a library that can be installed and imported across multiple projects.

Again, let’s consider what happens when import geometry is called. If there is no file called geometry.py in the present working directory, the next thing that Python will look for is a Python package called geometry. What is a Python package? It’s a folder that has a file called __init__.py. This can be imported just like a module, so if the folder is in your present working directory, importing it will execute the code in __init__.py. For example, if you were to put the functions you previously had in geometry.py in geometry/__init__.py you could import them from there.

More typically, a package might contain different modules that each has its own code. For example, we might organize our package like this:

| . | └── geometry | ├── init.py | └── circle.py

With the code that we previously had in geometry.py now in our circle.py module of the geometry package. To make the names in circle.py available to us we can import it explicitely like this:

>>> from geometry import circle
>>> circle.calculate_area

Or we can have the __init__.py file import it for us:

# __init__.py

from .circle import calculate_area, calculate_circ

This way, we can import our functions like this:

>>> from geometry import calculate_area

This also means that if we decide to add more modules into the package, the __init__.py file can manage all the imports from these modules. It can also perform other setup steps that you might want to do whenever you import the package.

Note: as your package becomes complex, you can create sub-packages by adding more directories with more __init__.py files into your package.

Now that you have your code in a package, you’ll want to install the code in your machine, so that you can import the code from anywhere on your machine (not only from this particular directory) and eventually also so that others can easily install it and run it on their machines.

To do so, we need to understand one more thing about the import statement. If import cannot find a module or package locally in the present working directory, it will proceed to look for this name somewhere in the Python path. The Python path is a list of file-system locations that Python uses to search for packages and modules to import. You can see it (and manipulate it!) here:

>>> import sys
>>> sys.path

So, we need to copy the code into one of the file-system locations that are stored in this variable. But no so fast! To not make a mess, let’s instead let Python do this for us. The setuptools library, part of the Python standard library that ships with the Python interpreter, is intended specifically for packaging and setup of this kind of thing. The main instrument for setuptools operations is the setup.py file, which we will look at next.