Python / Reliable programs / Lesson 7
Modules, packages, and isolated environments
Organize code into importable boundaries and distinguish an import package from an installed distribution and a virtual environment.
On this page
Lesson goals
- A module owns a namespace
- Record reproducibility and boundaries
A module owns a namespace
A module is an importable unit, commonly a Python source file, that provides a namespace. import math binds the module name, and math.isqrt selects one of its attributes. from math import isqrt binds that attribute directly in the current namespace. Explicit imports help readers discover where behavior comes from; wildcard imports obscure that information and invite collisions. Imports normally execute a module’s top-level code on first loading in a process and then reuse its cached module object. Therefore top-level network calls, prompts, or file modifications make a library surprising and can cause trouble before a caller even invokes a function.
Packages and entry points
A regular import package groups modules in a directory containing __init__.py; namespace packages are a separate supported mechanism without that regular-package marker. Keep an ordinary beginner package explicit before introducing more flexible layouts. Relative imports refer to the package’s structure and work best when the program is invoked as a module with python -m package.module. The __name__ value distinguishes importing a module from executing it as the main entry point. Put command-line orchestration in main and call it under the __name__ == "__main__" guard, leaving reusable functions available without triggering the application.
Use the library before adding dependencies
The standard library offers well-defined modules for many routine tasks. The first example uses integer square root, which avoids float conversion and gives the largest integer whose square does not exceed the input. Import only what clarifies the program; shortening every name is not a goal. A file named math.py in the working project can shadow the real library module, so choose project module names deliberately. Avoid diagnosing import problems by repeatedly changing search paths: first identify which interpreter runs, where the current module lives, and which module path was actually imported. These facts usually reveal the hidden mismatch.
import math
print(math.isqrt(80))
print(math.isqrt(81))
8
9
Eight squared is at most eighty while nine squared is larger; eighty-one has the exact integer square root nine.
An environment selects installed packages
A virtual environment isolates an interpreter’s package installation context from other projects. Create one with the intended interpreter using python -m venv .venv; activation merely adjusts shell convenience, and invoking the environment’s interpreter directly also works. Use that interpreter’s -m pip so package management targets the same environment. These are setup commands rather than runnable course examples, and no package installation is required here. An import package name and a distribution name are not guaranteed to match. The metadata example inspects the standard-library module itself, avoiding assumptions about installed third-party distributions and making the visible result portable.
import importlib.util
import sys
print(importlib.util.find_spec("json") is not None)
print("Python", sys.version_info.major)
True
Python 3
find_spec locates the standard json module without depending on third-party installation metadata. The major version remains stable across supported Python 3 interpreters.
Record reproducibility and boundaries
An environment is disposable configuration, not a folder to copy into version control or a security sandbox. Record supported Python versions and dependencies in project metadata, and regenerate environments from those records. Dependency pinning for an application and compatibility ranges for a reusable library solve different problems. A small public API helps modules evolve internally without forcing changes throughout the project. Keep configuration loading and command-line parsing near the outer boundary, then pass values into internal functions. When an import fails, verify package layout, invocation method, environment, and name shadowing in that order before inventing fallback imports that silently hide a broken installation.
Common errors and fixes
ModuleNotFoundError after installing a dependency
- Why it happens
- Installation and execution targeted different interpreters.
- How to fix it
- Use the running environment’s interpreter with -m pip.
- Prevent it next time
- Record interpreter selection in editor and shell configuration.
A local file hides a standard module
- Why it happens
- The project filename collides with an importable library name.
- How to fix it
- Rename the local module and inspect the imported module’s path.
- Prevent it next time
- Choose domain-specific module names instead of library names.
Importing a helper launches the whole program
- Why it happens
- Top-level code performs application orchestration immediately.
- How to fix it
- Move orchestration into main and guard its invocation.
- Prevent it next time
- Test importing modules without user interaction or external side effects.
Check your understanding
1. Explain how you would split calculation code from a command-line entry point.
Show worked solution
Put reusable pure functions in a domain module. In a separate entry module, parse arguments, call those functions, format results, and expose main. Guard the main call, then run with python -m the_package.entry. Importing the domain module should not parse arguments or write files.
2. Why is copying a .venv directory to another machine an unreliable deployment plan?
Show worked solution
The environment may embed interpreter paths and contain platform-specific artifacts. Recreate it with a supported interpreter and recorded dependency metadata on the target system, then verify imports and tests there. The portable unit is the source and reproducible specification, rather than assumptions about one machine’s environment directory.
Key takeaways
- Imports expose namespaces and execute initialization.
- Use the intended interpreter for both execution and installation.
- A virtual environment isolates dependencies but is not a security boundary.