Python Packages

Introduction

A Python package is a directory that contains one or more modules. Python packages allow developers to structure their code into logical units, making it easier to manage, reuse, and share. They also facilitate the installation and distribution of Python code through tools like pip, making it simple for others to use your code in their projects.

Creating Python Packages

To create a Python package, follow these steps:

  1. Create a directory for your package with the same name as the package. It is conventional to name your packages with all lower-case letters, and underscores _ separating the words.
  2. Inside the package directory, create a Python file for each module you want to include.
  3. Optionally, include an __init__.py file in the package directory. While this file is not strictly required in newer versions of Python, it’s still a good practice to include it to indicate that the directory is a package.

For its modules to be accessible for import, a package must be placed within the Python module search path (for example, the same directory as the importing file), just as a single module would.

Here’s what the structure of your directory might look like for a package that contains some geometry related functions:

my_geometry/
│
├── __init__.py
├── area.py
└── volume.py

Next, let’s populate the area.py and volume.py files with some appropriate functions for calculating areas and volumes.

Example:

# area.py

import math

def rectangle(width, height):
    return width * height

def circle(radius):
    return math.pi * radius ** 2
# volume.py

import math

def box(length, width, height):
    return length * width * height

def sphere(radius):
    return (4 / 3) * math.pi * radius ** 3

def cylinder(radius, height):
    return math.pi * radius ** 2 * height

With these files in place, we’ve created a Python package named my_geometry that contains the area and volume modules.

Importing Modules From Within Packages

Now that we have our package ready, let’s see how to import and use the modules within it.

Importing Using the Dot Notation

A module that’s inside a package can be imported by prefixing it with the package name and a dot (i.e. package_name.module_name) in an import. It will then be accessible using the package_name.module_name name.

Here is the basic syntax for using the dot notation with an import statement:

import package_name.module_name

Example:

import my_geometry.area

print(f"The area of the circle is: {my_geometry.area.circle(2)}")

Output:

The area of the circle is: 12.566370614359172

In the example above, the area module of the my_geometry package is imported using the dot notation my_geometry.area (line 1). Its circle() function is accessed in the print statement using the dot notation as well (line 3).

By using the dot notation, you can also import an item from a module with an importfrom statements. Using this syntax, the item will be accessible directly, just as it is with any fromimport statement.

Here is the basic syntax for using the dot notation with a fromimport statement:

from package_name.module_name import item_name

Example:

from my_geometry.volume import box

print(f"The volume of the box is: {box(2, 5, 10)}")

Output:

The volume of the box is: 100

In this example, the box() function of the volume module is imported (line 1). The module’s full name, my_geometry.volume, is specified with the dot notation during import. The box() function is accessed directly in the print statement without any prefix (line 3).

With the dot notation, you can use the same syntax and all the capabilities of the non-package imports, as the lesson about module imports discusses.

Note that importing the box() function from it’s module in the example above might not be optimal from a code clarity stand point. It’s better understood what volume.box() (i.e. with the package name and function name) does than the stand-alone function name box().

Importing Modules With the fromimport Statement

Another way to import a module from a package is with the fromimport statement. This is analogous to importing an item from a module, but instead of doing that, you are importing a module from a package.

It is important to understand that there are two different types of fromimport statements. One imports items from modules, and the other (described in this section) imports modules from packages.

Here is the basic syntax for importing a module from a package with fromimport:

from package_name import module_name

Example:

from my_geometry import volume

print(volume.cylinder(2, 10))

Output:

125.66370614359172

The example above prints the volume of a cylinder of radius 2 and height 10 (line 3). The volume module is imported with a fromimport statement (line 1), and therefore can be used directly, with no package prefix (line 3).

Aliasing Imported Modules

The as keyword can alias modules that are in packages the same way it can alias modules that are not. Aliasing is especially useful for modules that are within packages because it eliminates the necessity of referencing them with an extra long name that includes the package prefix.

Example:

import my_geometry.area as ar

print(ar.circle(2))

Output:

12.566370614359172

The example above shortens the name my_geometry.area to ar by aliasing it during import (line 1). The alias is then used in the statement that prints the area of a circle with radius 2 (line 3).

The as keyword can alias modules imported with a fromimport the same way it aliases items.

Example:

from my_geometry import volume as vol

print(vol.sphere(2))

Output:

33.510321638291124

The example above shortens the module name volume to vol by aliasing it during the import in a fromimport statement (line 1). The alias is then used in the statement that prints the volume of a sphere with radius 2 (line 3).

Importing Modules Using the Wildcard *

The asterisk * can be used as a wildcard when importing modules from a package. (It’s similar to how it can be used to import all items from a module.)

However, before the wildcard can be applied to a package import, there is some configuration footwork to be done.

In order to enable wildcard imports for a package, you must first specify the modules that will be imported with the wildcard in the package’s __init__.py file. To do this, assign a list of module name strings to a variable called __all__ as follows:

# __init__.py

__all__ = ['module1_name',
           'module2_name',
           'module3_name',
           ... ]

When the wildcard is used in a fromimport statement for a package, all the modules the __all__ list specifies are be imported.

Once the__init__.py file is set up with __all__, you can do a wildcard import from its package with the following syntax:

from package_name import *

In the next example, we’ll set the __all__ list for the my_geometry package and use a wildcard to import its modules.

# __init__.py

__all__ = ['area',
           'volume',
          ]
# main.py

from my_geometry import *

print(f"The area of the circle is: {area.circle(2)}")
print(f"The volume of the box is: {volume.box(2, 5, 10)}")

Output:

The area of the circle is: 12.566370614359172
The volume of the box is: 100

The first code snippet in the example above is the content of the __init__.py. It sets the __all__ list with both the area and volume module names. The second code snippet is the content of the main script file main.py that uses the modules. It employs the wildcard import for the my_geometry package (line 3). Since the area and volume modules are contained in __all__ , both are imported at this point. The code accesses them directly to print the output without having to use a package name prefix (lines 5 and 6).

Subpackages

Subpackages are Python packages inside other packages. They allow you to further organize your code into a hierarchy of modules.

Creating Subpackages

Creating a subpackage is as straightforward as placing a package in a directory of its own within the directory of another package.

Suppose for example that we want to expand the my_geometry package into a package that includes modules for additional branches of mathematics. We’ll create a main package for our math modules called my_math, and underneath it we’ll have a geometry subpackage.

The directory structure is as follows:

my_math/
│
└── geometry/
    │
    ├── __init__.py
    ├── area.py
    └── volume.py

Now, if we want to add another subpackage for trigonometry, for example, containing two modules, we can do it with the following structure:

my_math/
│
├── geometry/
│   │
│   ├── __init__.py
│   ├── area.py
│   └── volume.py
│
└── trigonometry/
    │
    ├── trig_functions.py
    └── trig_identities.py

It’s also possible to have modules at different levels. Suppose we have our own algebra code that’s just one module. We can add it right under my_math without making it a subpackage:

my_math/
│
├── algebra.py
├── geometry/
│   │
│   ├── __init__.py
│   ├── area.py
│   └── volume.py
│
└── trigonometry/
    │
    ├── trig_functions.py
    └── trig_identities.py

Using Subpackages

Subpackages behave exactly like other Python packages, but they need to be referenced with their package hierarchy when dot notation is used.

Example:

from my_math.geometry.volume import box

print(f"The volume of the box is: {box(2, 5, 10)}")

Output:

The volume of the box is: 100

The example above references the my_math.geometry.volume module using its full package hierarchy to import the box() function with a feromimport statement (line 1). The box() function is then used normally in the print statement (line 3).

Example:

import my_math.geometry.area

print(f"The area of the circle is: {my_math.geometry.area.circle(2)}")

In this example, the area package is imported by referencing its full hierarchy my_math.geometry.area (line 1). When the circle() function is used (line 3) it’s referenced using the dot notation and the full hierarchy again because the import statement was used as opposed to the fromimport, as in the previous example.

Using the __init__.py File

As you learned earlier, __init__.py is an optional file placed in the package’s directory. It’s good practice to create it in order to make it clear that the directory is a Python package, and it’s useful for executing package initialization code.

Execution of the Code in the __init__.py File

An __init__.py file is loaded and executed when a package or subpackage is involved in an import for the first time.

If you import a subpackage, Python will execute the __init__.py files of all its ancestor packages starting with the top-most package down to the __init__.py of the specific subpackage being imported.

Let’s see this in action with an example that has the following directory structure:

my_math/
│
├── __init__.py
│
├── geometry/
│   ├── __init__.py
│   ├── area.py
│   └── volume.py

Here we have the familiar geometry subpackage from before, which is under the my_math package, and we’ve added an __init__.py to the top-level my_math package.

Next we’ll add some tracer print statements to the __init__.py files and see what happens when we run a script (main.py) that imports the packages.

Example:

# __init__.py of the my_math package

print("Hello from my_math!")
# __init__.py of the geometry subpackage

print("Hello from geometry!")

__all__ = [
    'area',
    'volume'
# main.py

from my_math.geometry import area
from my_math.geometry import volume

print(f"The area of the circle is: {area.circle(2)}")
print(f"The volume of the box is: {volume.box(2, 5, 10)}")

Output:

Hello from my_math!
Hello from geometry!
The area of the circle is: 12.566370614359172
The volume of the box is: 100

In the example above, both __init__.py files now have a print statement (line 3 of each) that indicate which __init__.py file they are in. The main script main.py imports modules area and volume (lines 3 and 4) from the my_math.geometry submodule.

As you can see from the output, my_math‘s __init__.py executes first, printing “Hello from my_math!”, and then geometry‘s __init__.py, printing “Hello from geometry!”. The print statements in the main code (lines 6 and 7) run after that. Also note that even though main.py imports from my_math.geometry twice, each of the __init__.py files runs only once.

Defining Items in __init__.py

If you define items in the __init__.py file of a package, they can be imported as if the package is a module.

Example:

import math

# __init__.py of the geometry subpackage

__all__ = [
    'area',
    'volume'
    
QUARTER_ANGLE = math.pi/2.0
# main.py

from my_math.geometry import QUARTER_ANGLE

print(QUARTER_ANGLE)

Output:

1.5707963267948966

The example above initializes the constant QUARTER_ANGLE in the __init__.py file of the geometry package (line 9). The main script main.py can now import it from the geometry package with the same fromimport syntax used for importing an item from a module (line 3).

Relative Package Imports

You can import items from a subpackage within a parent package without referencing the parent hierarchy explicitly. It’s useful because it allows you to change your package’s position in the hierarchy without altering its import statements.

This is done by placing a dot . character in front of the subpackage name in a fromimport statement with the following syntax:

from .subpackage_name import item_name

It works with multiple item and wildcard imports as well.

Let’s set up the next example with a module placed inside the my_math package. This module is called interactive and is responsible for providing terminal UIs for doing calculations. Here is how it’s placed in the package directory structure:

my_math/
│
├── __init__.py
├── interactive.py
│
├── geometry/
│   ├── __init__.py
│   ├── area.py
│   └── volume.py

The next example shows the interactive module code, as well as its usage within the main.py script.

Example:

# interactive.py

from .geometry.area import circle

def calculate_circle_area():
    r = float(input("Enter the radius of your circle: "))
    print(f"The area of the circle is: {circle(r)}")
# main.py

from my_math.interactive import calculate_circle_area

calculate_circle_area()

Output:

Enter the radius of your circle: 2
The area of the circle is: 12.566370614359172

In the example above, the interactive.py module file imports the circle() function from the geometry.area subpackage (line 3). A dot is placed in front of the subpackage name, and therefore, the subpackage specified is relative to my_math, which is the package the interactive module is in. The module imports circle() because it is used in its calculate_circle_area() function (line 7).

The main script file, main.py imports calculate_circle_area() normally (line 3) and uses it (line 5) to prompt the user for a radius and print the area of the circle.

Summary & Reference for Python Packages

Python packages are a way to structure and organize module code in Python projects.


To create a Python package, follow these steps:

  1. Create a directory for your package with the same name as the package. It should have all lower-case letters, with underscores _ separating the words.
  2. Place the packages module files inside the directory.
  3. Optionally, include an __init__.py file in the package directory.
my_package/
│
├── __init__.py
├── module1.py
└── module2.py

Modules within a package can be imported with an import statement using the dot notation, package_name.module_name. Modules imported this way are accessible using package_name.module_name as well.

import package_name.module_name

You can also import an item from a module with an importfrom statements using the dot notation, package_name.module_name. The imported item with this syntax will be accessible directly with the item name only.

from package_name.module_name import item_name

Another way to import a module from a package is with the fromimport statement. This is analogous to importing an item from a module, but instead of doing that, you are importing a module from a package. The modules will be accessible directly without a package prefix.

from package_name import module_name

The as keyword can alias modules that are inside packages the same way it can alias modules that are not.


Wildcard imports of modules from a package are possible after specifying module names in the package’s __init__.py file. This imports all specified modules and allows direct use without package prefixes.

# __init__.py

__all__ = ['module1_name`,
           'module2_name`,
           'module3_name',
           ... ]
from package_name import *

A subpackage is nested package within another package. You can create one by placing its directory within the directory of another package.


Subpackages behave exactly like other Python packages, but they need to be referenced with their package hierarchy when dot notation is used.

from package.subpackage1.subpackage1 import item_name

If you import a subpackage, Python will execute the __init__.py files of all its ancestor packages starting with the top-most package down to the __init__.py of the specific subpackage being imported.


Items defined in the __init__.py file of a package can be imported as if the package is a module.

# __init__.py of a package

my_item = ...
from package_name import my_item

You can import items from a subpackage within a parent package without referencing the parent’s hierarchy explicitly. This is done by placing a dot . character in front of the subpackage name in a fromimport statement.

from .subpackage_name import item_name