Previous | Tutorial index | Next
Explain what packages are, what files are required, and how they are structured in a file system.
In Tutorial 1, you learned that a module (a single .py file) is like a toolbox – it groups related functions, classes, and variables. But what happens when your project grows to hundreds of toolboxes? You wouldn't throw all your tools into one giant box; you would organize them into categories (e.g., "Electrical Tools", "Plumbing Tools", "Carpentry Tools") and place them on different shelves or in different rooms.
In Python, packages are the "rooms" or "shelves" that hold your modules. They allow you to structure your code hierarchically, making large projects manageable, logically organized, and easy to navigate. Packages are essential for distributing libraries to other developers.
Technical Definition:
A package is a directory (folder) that contains a special file named __init__.py and one or more module files (.py files). It is a way of structuring Python's module namespace by using "dotted module names".
For example, the module name A.B designates a submodule named B in a package named A. This hierarchical naming prevents collisions. If two different packages have a module named utils, you can distinguish them via package1.utils and package2.utils.
database/ package).company.hr.employee vs company.finance.account).pip are packages).It is crucial to distinguish these two fundamental concepts. Let's break them down side-by-side:
| Feature | Module | Package |
|---|---|---|
| File System Representation | A single .py file. |
A directory (folder). |
| Required File | Only the .py file itself. |
Must contain an __init__.py file (for regular packages). |
| Purpose | Holds a set of related functions, classes, and variables for a specific task. | Holds a collection of related modules and sub-packages for a broader domain. |
| Namespace Example | import math (where math.py is the file). |
import xml.etree (where xml is a package, and etree is a sub-package or module inside it). |
| Analogy | A single book (e.g., "Math Handbook"). | A bookshelf or library section containing many books (e.g., "Science Section"). |
__init__.py FileThe __init__.py file is the most important distinguishing feature of a package. It is executed automatically when the package (or any module inside it) is imported for the first time.
Before Python 3.3, an __init__.py file was strictly required for a directory to be recognized as a package. Without it, Python would raise a ImportError and treat the directory as just a normal folder. While Python 3.3+ introduced "namespace packages" which don't require this file, it is highly recommended and considered best practice to always include __init__.py for explicit, regular packages. It ensures backward compatibility and provides a clear signal that this folder is part of your code base.
You can place any Python code inside __init__.py. This code runs the very first time you import anything from the package. It is often used to:
Example (shapes/__init__.py):
# shapes/__init__.py
print("Initializing the 'shapes' package...")
# Setup a global package-level variable
__version__ = "1.0.0"
# Code to run automatically on import
import os
if not os.path.exists("data"):
print("Warning: 'data' folder not found.")
__all__ for Wildcard ImportsWhen a user types from shapes import *, Python looks for a list named __all__ inside the __init__.py file to determine which modules should be imported. If __all__ is not defined, from package import * imports nothing (except the package itself) – it does not automatically load all submodules.
Example:
# shapes/__init__.py
__all__ = ['circle', 'rectangle', 'triangle'] # Only these modules will be imported on `from shapes import *`
You can pre-import key functions or classes from submodules inside __init__.py so that users can access them directly from the package level, without having to drill down into submodules.
Instead of:
from shapes.circle import area
from shapes.rectangle import area
You can do this in __init__.py:
# shapes/__init__.py
from .circle import area as circle_area
from .rectangle import area as rect_area
Now the user can simply do:
import shapes
shapes.circle_area(5) # Clean and simple!
Packages can contain sub-packages to create a deep, hierarchical tree. This is extremely useful for large frameworks (e.g., Django or Flask) where features are categorized into several layers.
Example File System Tree:
company/ # Top-level package
├── __init__.py # company package init
├── hr/ # hr sub-package
│ ├── __init__.py # hr package init
│ ├── employee.py # Module for employee management
│ └── recruitment.py # Module for hiring processes
├── finance/ # finance sub-package
│ ├── __init__.py # finance package init
│ ├── accounting.py # Module for accounting
│ └── payroll.py # Module for payroll processing
└── it/ # it sub-package
├── __init__.py
├── hardware.py
└── software.py
To access the payroll module, you use dot notation:
import company.finance.payroll
company.finance.payroll.calculate_salary()
Or, for a cleaner import:
from company.finance import payroll
payroll.calculate_salary()
Or, for a specific function:
from company.finance.payroll import calculate_salary
calculate_salary()
Python uses the same sys.path search list to find packages as it does for modules. When you type import company, Python looks for a directory named company that contains an __init__.py file inside each of the directories listed in sys.path.
Important: The parent directory of your package must be in sys.path, not the package directory itself.
/home/user/projects/company/, then /home/user/projects/ must be in sys.path.sys.path).Test your foundational knowledge before proceeding to the practical exercises.
Q1: Which file is strictly required to mark a standard directory as a Python package?
A) main.py
B) __init__.py
C) package.json
D) __main__.py
Q2: What is the primary difference between a module and a package?
A) A module can contain functions, but a package cannot.
B) A module is a single file, while a package is a directory containing modules and an __init__.py file.
C) Packages cannot contain sub-packages.
D) Modules are built-in, while packages are always third-party.
Q3: If you define __all__ = ['module_a', 'module_b'] in your __init__.py, what happens when a user runs from package import *?
A) Python raises an error.
B) Only module_a and module_b are imported.
C) All modules inside the package are imported.
D) Nothing happens; the * is ignored.
Q4: True or False: Code written inside __init__.py runs every single time you import a module from that package.
Q5: You have a package called animals with a sub-package mammals and a module dog.py. How do you import the bark() function from dog.py using a single line of code, while keeping the import statement concise?
A) import animals.mammals.dog.bark
B) from animals.mammals.dog import bark
C) from dog import bark
D) import animals.mammals.dog
These exercises guide you through creating, structuring, and importing packages on your own machine.
Goal: Create a simple package and verify that Python recognizes it.
Instructions:
On your computer, create a new folder called vehicles.
Inside the vehicles folder, create an empty file named __init__.py. (On Windows, you can right-click > New > Text Document and rename it carefully; on Mac/Linux, use touch __init__.py).
Inside the vehicles folder, create two new Python files: car.py and bike.py.
Write the following code inside car.py:
# car.py
def start():
return "Car engine starts with a key."
def honk():
return "Beep! Beep!"
Write the following code inside bike.py:
# bike.py
def start():
return "Bike engine starts with a kick."
def ring_bell():
return "Ring! Ring!"
Open your terminal, navigate to the parent directory containing the vehicles folder (not inside it).
Launch the Python interpreter (python).
Type import vehicles. Does it raise an error? (It shouldn't if __init__.py is present).
Type import vehicles.car.
Type vehicles.car.honk() and observe the output.
Type vehicles.bike.start() (Wait! This will raise an AttributeError because you haven't imported bike yet).
Type import vehicles.bike and then try vehicles.bike.start() again. It should work now.
__init__.py to Simplify ImportsGoal: Modify __init__.py so that users can call functions directly from the package level.
Instructions:
Open the vehicles/__init__.py file in your code editor.
Add the following lines to pre-import the start functions:
# vehicles/__init__.py
from .car import start as car_start
from .bike import start as bike_start
(The . before car means "from the same directory/package").
Save the file.
In the Python interpreter (make sure you restart it, or use import importlib; importlib.reload(vehicles)), try this:
import vehicles
print(vehicles.car_start()) # Output: Car engine starts with a key.
print(vehicles.bike_start()) # Output: Bike engine starts with a kick.
Notice that you didn't have to import car or bike separately! The __init__.py handled it for you, creating a clean, user-friendly interface.
Goal: Add a sub-package to your vehicles package.
Instructions:
Inside the vehicles folder, create a new folder named electric.
Inside the electric folder, create an empty __init__.py file.
Inside the electric folder, create a module named tesla.py with this content:
# vehicles/electric/tesla.py
def start():
return "Tesla starts silently with a push of a button."
def autopilot():
return "Engaging autopilot mode..."
In your terminal (Python interpreter), import the nested module:
from vehicles.electric import tesla
print(tesla.autopilot())
Alternatively, import the function directly:
from vehicles.electric.tesla import start
print(start()) # Output: Tesla starts silently...
| Problem | Likely Cause | Solution |
|---|---|---|
ModuleNotFoundError: No module named 'my_package' |
Python cannot find the package directory. | Ensure the parent directory of my_package is in sys.path. Print sys.path to debug. |
ImportError: attempted relative import with no known parent package |
You are trying to use relative imports (e.g., from . import module) in a script that is run directly as __main__. |
Relative imports only work inside packages. Use absolute imports (e.g., from package import module) in your main scripts, or restructure your project. |
The __init__.py file is not running. |
You might be using an old cached version of the package. | Restart your Python interpreter, or delete the __pycache__ folder and use importlib.reload(). |
Your package is empty when you do dir(package). |
You haven't defined __all__ nor imported anything in __init__.py. |
Python packages are not automatically scanned for modules upon import for performance reasons. Explicitly import submodules in __init__.py or import them directly. |
These questions require deeper analysis, code writing, and planning.
Task:
You are building a music player application. Create a package called music_library with the following structure:
music_library/
├── __init__.py
├── playlist.py (contains functions: create_playlist(), add_song(), remove_song())
├── player.py (contains functions: play(), pause(), skip())
└── metadata.py (contains a dictionary: SONG_DATA = {"song1": "Artist A", "song2": "Artist B"})
Requirements:
print() statements to simulate the functions).music_library/__init__.py, do the following:
__all__ so that from music_library import * imports playlist and player only (not metadata).play() function from player.py so that a user can call music_library.play() directly.main.py script in the parent directory that imports the package and demonstrates using all its features.Submission: Provide the full code for all files.
music_library/playlist.py:
# playlist.py
def create_playlist(name):
print(f"Creating playlist: {name}")
def add_song(song, playlist):
print(f"Adding {song} to {playlist}")
def remove_song(song, playlist):
print(f"Removing {song} from {playlist}")
music_library/player.py:
# player.py
def play(song):
print(f"Playing: {song}")
def pause():
print("Paused")
def skip():
print("Skipping to next song")
music_library/metadata.py:
# metadata.py
SONG_DATA = {
"song1": "Artist A",
"song2": "Artist B",
"song3": "Artist C"
}
music_library/init.py:
# __init__.py
from .playlist import create_playlist, add_song, remove_song
from .player import play, pause, skip
from .player import play as play_song # Pre-import for direct access
__all__ = ['create_playlist', 'add_song', 'remove_song', 'play', 'pause', 'skip']
main.py:
# main.py
import music_library as ml
ml.create_playlist("My Favorites")
ml.add_song("song1", "My Favorites")
ml.play_song("song1")
ml.pause()
ml.remove_song("song1", "My Favorites")
__init__.py - Analyzing the OutputTask: Consider the following package structure:
test_pkg/
├── __init__.py
└── module_x.py
__init__.py contains:
print("Initializing test_pkg...")
VERSION = "1.0"
def get_version():
return VERSION
module_x.py contains:
print("Loading module_x...")
from test_pkg import get_version
print(f"Module X says version: {get_version()}")
Now, a user runs the following script in the parent directory:
import test_pkg
print("-- First import done --")
import test_pkg.module_x
print("-- Second import done --")
import test_pkg.module_x
Assignment:
Exact output:
Initializing test_pkg...
-- First import done --
Loading module_x...
Module X says version: 1.0
-- Second import done --
Explanation:
When import test_pkg executes, Python runs the code in __init__.py, printing "Initializing test_pkg...". The -- First import done -- print occurs immediately after. When import test_pkg.module_x executes, Python loads module_x.py, which prints "Loading module_x..." and then calls get_version(). The -- Second import done -- print occurs after. The second import test_pkg.module_x does NOT re-execute the module because it is already cached in sys.modules. This demonstrates Python's module caching mechanism, which prevents redundant loading and execution of module code.
Task: You are tasked with building a simple E-Commerce system. It will handle:
Assignment: Design a complete package hierarchy (draw a tree diagram or use text) for this system.
ecommerce/
├── __init__.py
├── users/
│ ├── __init__.py
│ ├── auth.py (register_user(), login_user())
│ └── profiles.py (update_profile(), get_user_history())
├── products/
│ ├── __init__.py
│ ├── catalog.py (add_product(), list_products())
│ └── inventory.py (update_stock(), check_availability())
└── orders/
├── __init__.py
├── checkout.py (create_order(), calculate_total())
├── payment.py (process_payment(), refund_payment())
└── invoice.py (generate_invoice(), send_invoice_email())
Defense: This structure separates concerns into three clear domains: user management, product management, and order processing. Each sub-package groups related modules, making it easy for developers to locate and modify specific features. For a team of 5 developers, this organization minimizes merge conflicts by ensuring that different developers can work on users/, products/, and orders/ independently. The modular structure also allows for independent testing of each component, improves readability, and facilitates gradual extension of the system (e.g., adding a new payment gateway would only affect the orders/payment.py module).
Task: You are given a broken package structure. A developer sends you the following folder:
shopping/
__init__.py # Contains: __all__ = ['cart']
cart.py # Contains: def total_price(items): return sum(items)
checkout.py # Contains: def confirm(): print("Confirmed")
The user tries to run this code and gets errors:
from shopping import *
cart.total_price([10, 20]) # Works fine.
checkout.confirm() # Raises NameError: name 'checkout' is not defined
Questions:
checkout.confirm() raise a NameError even though checkout.py exists in the folder?checkout.confirm() directly). How would you modify the __init__.py file?import shopping and then tried shopping.checkout.confirm(), what would need to be changed in the package to make that work?1. Why the error occurs:
The NameError occurs because __all__ = ['cart'] in __init__.py tells Python to only import the cart module when from shopping import * is used. The checkout module is not imported, so the name checkout doesn't exist in the current namespace.
2. Fix by modifying __init__.py:
# shopping/__init__.py
__all__ = ['cart', 'checkout']
from . import cart, checkout
This makes both modules available when from shopping import * is used.
3. Making import shopping; shopping.checkout.confirm() work:
To make this syntax work, __init__.py would need to import checkout so that it becomes an attribute of the shopping package:
# shopping/__init__.py
from . import cart, checkout
(Note: This also satisfies the wildcard import requirement if __all__ is also defined.)
Task: You have this package:
animals/
├── __init__.py
├── wild.py
└── domestic/
├── __init__.py
├── dog.py
└── cat.py
Inside wild.py, you want to import the Dog class from domestic.dog.
Inside domestic/dog.py, you want to import a function from wild.py.
Assignment:
import statement for wild.py to import Dog.import statement for domestic/dog.py to import a function named roar() from wild.py.1. Import in wild.py:
from animals.domestic.dog import Dog
(Using an absolute import)
2. Import in domestic/dog.py:
from ..wild import roar
(Using a relative import)
3. Difference between absolute and relative imports:
| Aspect | Absolute Import | Relative Import |
|---|---|---|
| Syntax | from package.subpackage import module |
from . import module or from .. import module |
| Clarity | More explicit; always clear where it comes from | Less explicit; can be confusing in deeply nested structures |
| Portability | More robust to package renaming (only the package name changes) | Fragile; breaks if the package is moved or renamed |
| Use Case | Preferred for most scripts and main applications | Useful within a package to refer to sibling modules without hardcoding the full path |
Recommendation: Use absolute imports for clarity and maintainability, especially when sharing code with others. Use relative imports only within a package for convenience, and only when the directory structure is stable.
This concludes the Tutorial 2. Once you have completed the exercises and homework, you will have a solid practical and theoretical grasp of Python packages. You are now ready to move on to Tutorial 3, where we will learn how to import these packages and modules using various import statements!