By WebNest Studio Engineering Team
Python Tutorial
Python Packages and Project Structure
A package is a way of grouping related modules together under a common namespace, using the filesystem's directory structure. Where a module is a single .py file, a package is a directory containing multiple modules (and possibly sub-packages), which lets large projects stay organized as they grow beyond what a handful of flat files can manage.
Packages also give you dotted import paths, like import requests.adapters or from django.db import models, which make it immediately clear where functionality lives within a large codebase.
Why it matters
Once a project has more than a handful of files, they are grouped into folders, and those folders are packages. Every library you install is a package, and import lines such as from app.services.email import send describe a path through one. Understanding the layout is what lets you organise your own project and fix import errors in it.
The Role of __init__.py
Historically, any directory containing an __init__.py file was treated by Python as a regular package. That file can be completely empty — its mere presence used to be what told Python "treat this folder as a package, not just a random directory." Since Python 3.3, "namespace packages" allow directories without __init__.py to work as packages too, but including an explicit __init__.py is still the clearer, more common, and more predictable approach, especially for anything you intend to distribute. The file is also a natural place to run package-level setup code or to explicitly control what a from package import * exposes, using an __all__ list.
A Typical Package Layout
A simple package structure for a project called "shop" might look like this, with each subdirectory representing a sub-package.
shop/__init__.py— marks shop as a package, may re-export commonly used names.shop/models.py— a module inside the package.shop/orders/__init__.py— a nested sub-package.shop/orders/checkout.py— a module inside the sub-package, imported asshop.orders.checkout.
Relative vs Absolute Imports
An absolute import spells out the full path from the project's top-level package, such as from shop.orders import checkout. A relative import instead uses leading dots to describe a module's location relative to the current one — a single dot (.) means "the current package," and two dots (..) mean "the parent package," as in from . import models or from ..utils import helpers. Absolute imports are generally preferred in application code because they are unambiguous and easy to search for, while relative imports are common inside packages that are meant to be self-contained and movable as a unit. Critically, relative imports only work inside a package that was itself imported — you cannot run a file containing relative imports directly as a script.
Example
Package structure with absolute and relative imports
# Project layout:
# shop/
# __init__.py
# models.py
# orders/
# __init__.py
# checkout.py
# --- shop/models.py ---
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# --- shop/orders/checkout.py ---
from ..models import Product # relative import: go up one package to models
def total_price(products):
return sum(p.price for p in products)
# --- run_shop.py at the project root ---
from shop.models import Product # absolute import
from shop.orders.checkout import total_price
cart = [Product("Book", 15.0), Product("Pen", 2.5)]
print(total_price(cart))
17.5
Common Mistakes
- Trying to run a module containing relative imports directly with "python checkout.py", which fails because relative imports require the file to be part of an imported package, not the __main__ script.
- Forgetting __init__.py in older codebases or tooling that assumes regular packages, leading to "module not found" errors even though the directory and files clearly exist.
- Creating circular imports between two modules that each import from the other at the top level, which raises an ImportError partway through initialization.
- Confusing a package (a directory of modules) with a distribution/library installed via pip — a single pip-installed library can contain many packages and modules internally.
Exercise
urllib is a package in the standard library and parse is a module inside it. Import urlparse from that module with an absolute import, parse the address below, and print its host and its path. Then print the full dotted name of the module that urlparse comes from.
Expected output: example.com, /docs, urllib.parse (one per line)
# TODO: import urlparse from the parse module of the urllib package
address = "https://example.com/docs?page=2"
# TODO: parse the address and print its netloc and its path
# TODO: print urlparse.__module__
Show hints
- The import is
from urllib.parse import urlparse: package, then module, then name. - The result of
urlparsehas the attributesnetlocandpath.
Show solution
from urllib.parse import urlparse
address = "https://example.com/docs?page=2"
parts = urlparse(address)
print(parts.netloc)
print(parts.path)
print(urlparse.__module__)
Quick quiz
1. What is the traditional role of __init__.py in a folder?
- A. It stores settings
- B. It marks the folder as a package and runs when the package is imported
- C. It is the entry point of the program
- D. It lists the dependencies
Show answer
B. It may be empty, or it can expose selected names from the modules inside.
2. Inside a package, what does from . import utils mean?
- A. Import utils from the standard library
- B. Import everything
- C. Import the utils module from the same package
- D. Import from the parent folder of the project
Show answer
C. A single dot means the current package; two dots mean its parent.
3. Which style of import does PEP 8 recommend in general?
- A. Relative imports
- B. Imports inside functions
- C. Star imports
- D. Absolute imports
Show answer
D. An absolute import states the full path and reads the same wherever the file is.
Interview questions
What is the difference between a module and a package?
Show answer
A module is a single .py file. A package is a folder of modules, and possibly of further packages, usually containing an __init__.py file. Packages give a project a hierarchy, so that modules are addressed with dotted names such as shop.orders.models.
What is the difference between absolute and relative imports?
Show answer
An absolute import gives the full path from the top of the project or from an installed package, as in from shop.orders import models. A relative import starts from the current package and uses dots, as in from . import models or from ..utils import helper. Relative imports work only inside a package and fail in a file that is run directly as a script.
Key Points to Remember
- A package is a directory of modules, traditionally marked with an __init__.py file.
- Namespace packages (no __init__.py) exist since Python 3.3 but explicit __init__.py remains the clearer convention.
- Absolute imports spell out the full dotted path; relative imports use leading dots relative to the current package.
- Relative imports only work when a module is imported as part of a package, never when run directly as a script.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs Python in your browser. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering