Modules & Packages
Creating Modules
Writing reusable Python modules with proper structure
Interview: Code organization and software design — tested in system design discussions
A Python module is simply a .py file containing Python code — functions, classes, variables, and executable statements. Creating well-structured modules is fundamental to code organization, reusability, and maintainability.
Module Structure
- Module file: Any .py file can be a module — just import it by filename (without .py)
- Module name: Available as
__name__— equals "__main__" when run directly - __all__: List of public names — controls what
from module import *exports - Module-level code: Runs on first import — use
if __name__ == "__main__":guard for scripts
if __name__ == "__main__" Guard
- When imported:
__name__equals the module's filename — guard block is skipped - When run directly:
__name__equals "__main__" — guard block executes - Purpose: Lets a file work as both a module (importable) and a script (runnable)
Module Search Path
Python searches for modules in: (1) built-in modules, (2) directories in sys.path. sys.path includes the script's directory, PYTHONPATH directories, and standard library paths. You can modify sys.path at runtime, but prefer proper package installation with pip.
Module Caching
- Imported once: Python caches modules in
sys.modules— subsequent imports return the cached version - Reloading: Use
importlib.reload(module)to force re-execution (useful during development) - .pyc files: Python compiles modules to bytecode in __pycache__ for faster loading
Use Cases
Organizing utility functions into reusable modules
Creating dual-purpose files that work as both scripts and importable modules
Building internal libraries shared across multiple projects
Plugin architectures with dynamic module loading
Configuration modules with module-level constants
Common Mistakes
Not using if __name__ == "__main__" guard — module code runs on every import
Putting side effects at module level — import should not print, create files, or modify state
Not defining __all__ — makes it unclear what the public API is
Using mutable module-level variables — creates hidden shared state between importers
Forgetting that modules are cached — changes to .py files require reload() or restart