HSHimanshu Sharma
Python Notes

Python modules and packages: import, __init__.py, relative imports and __name__

Python modules and packages: import, __init__.py, relative imports and __name__ Advertisement

A single Python file is fine for a small script. As a project grows, you need to split it into files and group those files into folders. Python has two words for this: a module is one .py file, and a package is a folder of modules. This post explains how to create both, the different ways to import from them, what __init__.py does, how relative imports and __name__ work, and how to fix the import errors everyone meets. The example project below was actually built and run.

Modules

Any Python file can be imported by its name without the .py. If helpers.py contains def greet(): ..., another file in the same folder can do:

import helpers
helpers.greet()

from helpers import greet      # import one name
import helpers as h            # give the module a short alias
from helpers import *          # import everything public (avoid in real code)

Prefer explicit imports. from x import * hides where names come from and can overwrite your own.

The first time a module is imported, Python runs it from top to bottom and stores the result in sys.modules. Later imports reuse that stored module, so top-level code runs only once per program.

The standard library is a collection of ready-made modules (math, os, json, re, datetime), and pip installs third-party ones.

Packages

Make a folder, put modules in it, and add a file named __init__.py:

shop/
  __init__.py
  tools/
    __init__.py
    price.py
    text.py
  models/
    __init__.py
    item.py
main.py

Here are the files (kept short on purpose):

# shop/__init__.py
print("shop package loaded")
VERSION = "1.0"
from .tools.price import with_tax
__all__ = ["with_tax", "VERSION"]
# shop/tools/price.py
TAX = 0.18
def with_tax(amount):
    return round(amount * (1 + TAX), 2)
# shop/tools/text.py
from .price import TAX
from ..models.item import Item
def label(item):
    return f"{item.name}: {item.price} (tax {TAX})"
# shop/models/item.py
class Item:
    def __init__(self, name, price):
        self.name, self.price = name, price
    def __repr__(self): return f"Item({self.name!r}, {self.price})"
if __name__ == "__main__":
    print("run as script:", __name__)
else:
    print("imported as:", __name__)

(shop/tools/__init__.py and shop/models/__init__.py are empty files.) And a script that uses the package:

# main.py
import shop
print(shop.VERSION, shop.with_tax(100))
from shop.models.item import Item
from shop.tools import text
print(text.label(Item("Tea", 50)))
import shop.tools.price as p
print(p.__name__, shop.__name__, shop.__package__, text.__package__)

Running python3 main.py from the project folder printed:

shop package loaded
1.0 118.0
imported as: shop.models.item
Tea: 50 (tax 0.18)
shop.tools.price shop shop shop.tools

Notice the order: the message from shop/__init__.py appears first, as soon as the package is imported, and the line from item.py appears when main.py imports it. Each module's own __name__ is its dotted path (shop.tools.price).

What __init__.py does

  • It marks a folder as a package (modern Python can also import folders without it, called namespace packages, but an explicit __init__.py is clearer and expected by most tools).
  • Its code runs once, when the package is first imported. Use it to set up the package, define a VERSION, or import the most useful names so users can write shop.with_tax(...) instead of shop.tools.price.with_tax(...).
  • __all__ lists the names that from shop import * should export.

Keep __init__.py light. Heavy work there slows down every import of the package.

Absolute and relative imports

An absolute import starts at the top of the package: from shop.tools.price import with_tax. It works the same from anywhere and is the recommended default.

A relative import starts from the current module's position: one dot is "this package", two dots is "the parent package".

Written inside shop/tools/text.py Means
from .price import TAX the module price in the same package
from ..models.item import Item models.item in the parent package shop
from . import price the price module from this package

Relative imports only work inside a package and when the file is imported as part of it. They make it easy to rename or move the whole package.

__name__ and running a file directly

Every module has a __name__. When you run a file directly, it is "__main__"; when it is imported, it is the module's name. That lets a file be both a library and a program:

if __name__ == "__main__":
    main()

The block runs only when the file is run, not when it is imported. In our example, running the module in two ways gave different results:

$ python3 -m shop.models.item        # run as part of the package
shop package loaded
run as script: __main__

$ python3 shop/models/item.py        # run as a plain script
run as script: __main__

The -m form imports shop first (so its message appears) and then runs the module. Running a file inside a package as a plain script breaks relative imports, because Python does not know it belongs to a package. If a file with from .price import TAX is run by path, it fails with ImportError: attempted relative import with no known parent package. Use python -m package.module for such files.

Where Python looks: sys.path

When you write import something, Python searches in order: the built-in modules, then each folder in sys.path, which holds the script's folder (or the current folder in an interactive session), the folders in the PYTHONPATH variable, the standard library and installed packages (site-packages).

import sys
print(type(sys.path))      # <class 'list'>

You can inspect sys.modules to see what has been imported ("shop" in sys.modules was True after the import), and module.__file__ to see where a module came from. To import by name at run time, use importlib.import_module("shop.models.item").

Do not edit sys.path as a normal practice. Better ways: run your program from the project root, install the project in a virtual environment (pip install -e .), or use python -m.

Common errors and fixes

Error Usual cause Fix
ModuleNotFoundError: No module named 'nothere' Misspelled name, not installed, or not on sys.path Check spelling; pip install; run from the project root
ImportError: cannot import name 'x' The module exists but has no such name Check the name and for circular imports
ImportError: attempted relative import with no known parent package A relative import in a file run as a script Run with python -m package.module
Importing your own file shadows a library You named a file random.py or json.py Rename it
Circular import Module A imports B and B imports A at top level Move shared code to a third module, or import inside a function

The shadowing problem is a favourite: a file called random.py in your folder will be imported instead of the standard library's, and strange errors follow.

Layout tips

  • Name modules in short lower-case words (price.py), packages likewise.
  • One clear responsibility per module.
  • Put tests in a separate tests/ folder.
  • Keep the package folder directly under the project root so python -m and imports work.
  • Document the public names, and keep internal helpers behind a leading underscore.

Practice

Create a package called utils with modules text.py (a function slug(title)) and numbers.py (average(values)), expose both from __init__.py, and call them from a main.py. Then add a if __name__ == "__main__": demo to numbers.py and run it both ways. Break the layout on purpose (rename __init__.py, run a file with a relative import by path) and read the errors.

Continue

Imports pair naturally with the other building blocks: language fundamentals, regular expressions (the re module) and logging (the logging package, which has its own submodules). Practise with the Python video course and check yourself with the interview questions.

Advertisement