Modules & Packaging

15 questions found

What is the difference between a module and a package in Python?

Beginner
A module is a single .py file containing Python definitions and statements that can be imported. A package is a directory containing multiple modules plus an __init__.py file (making it importable as a namespace), allowing related modules to be organized hierarchically, e.g., `import package.module`.
# mymodule.py is a module
import mymodule

# mypackage/ with __init__.py is a package
import mypackage.submodule
Real-world example A web application's codebase organizes routes.py, models.py, and utils.py as modules inside an `app` package, giving a clean `from app.models import User` import path.

Common follow-ups: Do packages require __init__.py in modern Python?;What is a namespace package?

Modules & Packaging;Virtual Environments & Dependency Management

What does the __init__.py file do inside a package directory?

Beginner
__init__.py marks a directory as a regular Python package and runs when the package is first imported, letting you control what's exposed at the package level (e.g., re-exporting submodule contents), set up package-wide state, or simply exist as an empty file to establish the package structure. Since Python 3.3, packages can technically work without it (namespace packages), but explicit __init__.py remains the common convention.
# mypackage/__init__.py
from .models import User
from .utils import helper

__all__ = ['User', 'helper']

# now: from mypackage import User
Real-world example A library's __init__.py re-exports key classes from internal submodules so users write `from mylib import Client` instead of the longer `from mylib.core.client import Client`.

Common follow-ups: What is __all__ and how does it affect `from package import *`?;What are namespace packages and how do they differ?

Modules & Packaging;Multiple Inheritance & MRO

How does Python's module import system avoid re-executing a module multiple times when imported from different places?

Intermediate
Python caches every imported module in sys.modules keyed by its fully qualified name. When any code does `import foo`, Python checks sys.modules first; if already present, it reuses the cached module object instead of re-running foo.py, ensuring module-level code executes exactly once per process and all importers share the same module object (and its state).
import sys

import json
print('json' in sys.modules)  # True after first import

# Subsequent imports reuse the cached module -- no re-execution
import json as json2
print(json is json2)  # True, same object
Real-world example A configuration module that loads settings once at import time relies on this caching -- every part of the application importing `config` gets the same already-initialized settings object, not a fresh reload.

Common follow-ups: How do you force a module to reload with importlib.reload()?;Why can module-level side effects be risky given this caching?

Modules & Packaging;Memory Management & Garbage Collection

What is the difference between an absolute import and a relative import within a package?

Intermediate
An absolute import specifies the full path from the top-level package (`from mypackage.utils import helper`), while a relative import uses leading dots to reference modules relative to the current module's position in the package (`from .utils import helper` for a sibling, `from ..core import Base` for a parent package). Relative imports only work within a package context (not in standalone scripts) and make internal restructuring easier since you don't hardcode the package's top-level name.
# Inside mypackage/subpkg/module.py
from . import sibling_module      # relative: same package
from .. import top_level_module   # relative: parent package
from mypackage.subpkg import sibling_module  # absolute equivalent
Real-world example A large internal package uses relative imports throughout its submodules so the entire package can be renamed or vendored into another project without rewriting every absolute import path.

Common follow-ups: Why do relative imports fail when a module is run directly as __main__?;When do style guides recommend absolute over relative imports?

Modules & Packaging;Functions & Scope

How does Python locate modules during import, and what is sys.path's role?

Advanced
When you import a module, Python searches directories listed in sys.path in order: the directory of the script being run (or '' for interactive/current directory), directories from the PYTHONPATH environment variable, and standard installation-dependent paths (including site-packages). The first matching module found wins, which is why sys.path order and accidental name shadowing (e.g., a local file named `json.py`) can cause confusing import bugs.
import sys
print(sys.path)  # list of directories searched, in order

sys.path.insert(0, '/custom/module/location')  # prioritize a custom path
import my_custom_module
Real-world example A developer's script mysteriously imports the wrong `requests` module because a local file named requests.py in the working directory shadows the installed package earlier in sys.path.

Common follow-ups: How do virtual environments modify sys.path?;What's the difference between sys.path and PYTHONPATH?

Virtual Environments & Dependency Management;Debugging & Profiling

What does the `if __name__ == '__main__':` idiom accomplish, and why is it important for modules?

Intermediate
Every module has a __name__ attribute set to '__main__' if run directly as a script, or to the module's actual name if imported elsewhere. Guarding script-execution code with `if __name__ == '__main__':` lets a file work both as a reusable importable module and as a standalone script, without running its script-only logic (like CLI parsing or test code) when imported by other code.
# mymodule.py
def main():
    print('Running as a script')

if __name__ == '__main__':
    main()  # only runs when executed directly, not when imported
Real-world example A data-processing module exposes reusable functions for import elsewhere, but also includes a `__main__` block so it can be run directly from the command line for quick manual testing.

Common follow-ups: How does this interact with the -m flag for running modules?;What does __name__ equal when run via `python -m package.module`?

Command-Line Interfaces (argparse);Modules & Packaging

What is a namespace package, and how does it differ from a regular package?

Advanced
A namespace package (PEP 420) is a package without an __init__.py that can span multiple directories or distributions -- Python implicitly recognizes any directory containing Python files as a namespace package portion, letting a single logical package (like `myorg.tools`) be split across separately installed distributions that merge into one importable namespace at runtime.
# No __init__.py needed:
# dist1/myorg/tools/a.py
# dist2/myorg/tools/b.py
# Both contribute to the same 'myorg.tools' namespace package

import myorg.tools.a
import myorg.tools.b
Real-world example A large organization splits a monorepo's internal tooling into separately versioned pip packages (myorg-tools-network, myorg-tools-storage) that all contribute submodules to the same `myorg.tools` namespace.

Common follow-ups: What are the downsides of namespace packages regarding import speed?;How do you explicitly declare a regular package to avoid ambiguity?

Virtual Environments & Dependency Management;Modules & Packaging

How does pyproject.toml serve as the modern standard for defining a Python package's build configuration and dependencies?

Intermediate
pyproject.toml (PEP 518/621) is the standardized configuration file that specifies build-system requirements (like setuptools or poetry), project metadata (name, version, dependencies), and tool-specific configuration (black, pytest, mypy) in one place, replacing the older, less standardized combination of setup.py, setup.cfg, and various tool config files.
# pyproject.toml
[project]
name = "mypackage"
version = "1.0.0"
dependencies = ["requests>=2.28", "click>=8.0"]

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
Real-world example A new open-source project adopts pyproject.toml exclusively, avoiding a legacy setup.py entirely, and configures both its build system and its black/pytest settings in the same file.

Common follow-ups: What's the difference between [project] and [tool.poetry] sections?;How does pip install -e . use pyproject.toml?

Virtual Environments & Dependency Management;Modules & Packaging

How does Python resolve circular imports, and what strategies avoid the resulting ImportError?

Advanced
A circular import occurs when module A imports module B, and B (directly or indirectly) imports A, potentially trying to access a name in A before A has finished executing (since A is partially initialized in sys.modules during the circular import). Common fixes include: restructuring code to remove the cycle, moving the import inside a function (deferred/local import) so it happens after both modules are fully loaded, or importing the module itself rather than specific names from it (`import a` then `a.something` instead of `from a import something`).
# a.py
import b
def func_a():
    return b.func_b()

# b.py
def func_b():
    import a  # deferred import avoids circular ImportError at module load time
    return 'from b'
Real-world example Two tightly-coupled modules in a Django app (models.py and signals.py) resolve a circular import by moving one import inside a function body, deferring it until after both modules have fully loaded.

Common follow-ups: Why does moving an import inside a function fix the cycle?;How does restructuring shared code into a third module help?

Modules & Packaging;Functions & Scope

What is __all__ and how does it control `from module import *` behavior?

Intermediate
__all__ is a list of strings defined at module level specifying exactly which names are exported when a client does `from module import *`. Without __all__, `import *` imports all public names (those not starting with an underscore); with __all__ defined, only the listed names are imported, giving the module author explicit control over its public API surface.
# mymodule.py
__all__ = ['public_func', 'PublicClass']

def public_func(): pass
def _private_helper(): pass
class PublicClass: pass

# from mymodule import *  only imports public_func and PublicClass
Real-world example A utility module defines __all__ to explicitly exclude internal helper functions from wildcard imports, keeping the importing namespace clean and preventing accidental use of implementation details.

Common follow-ups: Does __all__ affect explicit `from module import _private`?;Why is `import *` generally discouraged regardless?

Modules & Packaging;Functions & Scope

Showing 1–10 of 15