cyberangles blog

How to Access Module Objects from __init__.py in __main__.py: Python Guide

Python’s modular design encourages organizing code into packages and modules, making projects scalable and maintainable. Two critical files in Python packages are __init__.py and __main__.py. The __init__.py file initializes the package, while __main__.py serves as the entry point when the package is run as a script. A common challenge is accessing objects (variables, functions, classes) defined in __init__.py from __main__.py. This guide will demystify this process with step-by-step explanations, examples, and best practices.

2026-02

Table of Contents#

  1. Understanding Python Packages and Special Files
  2. Project Structure Setup
  3. Accessing Objects from __init__.py in __main__.py
  4. Advanced Scenarios: Exposing Submodule Objects via __init__.py
  5. Common Pitfalls and Solutions
  6. Best Practices
  7. Conclusion
  8. References

1. Understanding Python Packages and Special Files#

What is a Python Package?#

A Python package is a directory containing multiple Python modules (.py files) and a special __init__.py file. Packages allow you to organize related code into a hierarchy, making it easier to import and reuse. For example, a package named data_processing might contain modules for cleaning, transforming, and analyzing data.

Role of __init__.py#

The __init__.py file is executed when the package is imported. Its primary roles are:

  • Initialization: Run setup code (e.g., configuring logging, initializing constants).
  • Exposing Public API: Define which objects (functions, classes, variables) should be accessible when users import the package (via __all__).
  • Simplifying Imports: Import objects from submodules and expose them at the package level (e.g., from .submodule import MyClass so users can do from package import MyClass instead of from package.submodule import MyClass).

Role of __main__.py#

The __main__.py file is the entry point for the package when it is run as a script. When you execute a package with python -m package_name, Python runs the code in __main__.py. This is useful for creating command-line interfaces (CLIs) or standalone applications packaged as Python packages.

2. Project Structure Setup#

To follow along, let’s create a sample project. We’ll use a package named my_package with basic objects in __init__.py and a __main__.py that accesses them.

Sample Project Layout#

Here’s the directory structure we’ll use:

my_project/                  # Root directory (not part of the package)
└── my_package/              # The Python package
    ├── __init__.py          # Package initialization
    ├── __main__.py          # Entry point when run as a script
    └── utils.py             # A submodule (for advanced examples)

Creating __init__.py#

Let’s populate __init__.py with simple objects (variables, functions) to demonstrate access:

# my_package/__init__.py
 
# A package-level variable
PACKAGE_VERSION = "1.0.0"
 
# A package-level function
def greet(name: str) -> str:
    return f"Hello, {name}! Welcome to my_package v{PACKAGE_VERSION}."

Creating __main__.py#

For now, leave __main__.py empty. We’ll add code to access PACKAGE_VERSION and greet from __init__.py in the next section.

3. Accessing Objects from __init__.py in __main__.py#

Direct Import from the Package#

Since __main__.py is part of my_package, you can import objects directly from the package itself. Use the package name (my_package) as the import source.

Example: Importing PACKAGE_VERSION and greet into __main__.py
Update __main__.py as follows:

# my_package/__main__.py
 
# Import objects directly from the package
from my_package import PACKAGE_VERSION, greet
 
def main():
    print(f"Running my_package v{PACKAGE_VERSION}")
    message = greet("Python Developer")
    print(message)
 
if __name__ == "__main__":
    main()

Using Relative Imports#

Within a package, you can use relative imports to reference objects in sibling files. Since __main__.py and __init__.py are in the same directory (my_package), you can use from . import ... to import objects from __init__.py.

Example: Relative Import in __main__.py
Modify __main__.py to use relative imports:

# my_package/__main__.py
 
# Relative import from __init__.py (same directory)
from . import PACKAGE_VERSION, greet
 
def main():
    print(f"Running my_package v{PACKAGE_VERSION}")
    message = greet("Python Developer")
    print(message)
 
if __name__ == "__main__":
    main()

Relative imports are concise and work well for internal package references. Use them when importing within the same package.

Leveraging __all__ in __init__.py#

The __all__ variable in __init__.py explicitly defines the public API of the package. It specifies which objects are imported when users run from package import *. While not strictly required for accessing objects in __main__.py, __all__ improves readability and documentation.

Example: Adding __all__ to __init__.py
Update __init__.py to include __all__:

# my_package/__init__.py
 
PACKAGE_VERSION = "1.0.0"
 
def greet(name: str) -> str:
    return f"Hello, {name}! Welcome to my_package v{PACKAGE_VERSION}."
 
# Define public exports (for `from my_package import *`)
__all__ = ["PACKAGE_VERSION", "greet"]

Now, __main__.py can still import PACKAGE_VERSION and greet directly (as shown earlier), but __all__ clarifies which objects are intended for public use.

4. Advanced Scenarios: Exposing Submodule Objects via __init__.py#

A common pattern is to import objects from submodules (e.g., utils.py) into __init__.py, then expose them at the package level. This simplifies imports for users (and for __main__.py).

Example: Importing Submodule Code into __init__.py#

Let’s add a submodule utils.py with a helper function, then import it into __init__.py:

Step 1: Create utils.py

# my_package/utils.py
 
def calculate_average(numbers: list[float]) -> float:
    """Compute the average of a list of numbers."""
    return sum(numbers) / len(numbers) if numbers else 0.0

Step 2: Import calculate_average into __init__.py
Update __init__.py to expose calculate_average at the package level:

# my_package/__init__.py
 
PACKAGE_VERSION = "1.0.0"
 
def greet(name: str) -> str:
    return f"Hello, {name}! Welcome to my_package v{PACKAGE_VERSION}."
 
# Import from submodule and expose at the package level
from .utils import calculate_average
 
# Update __all__ to include the new public object
__all__ = ["PACKAGE_VERSION", "greet", "calculate_average"]

Accessing Exposed Submodule Objects in __main__.py#

Now __main__.py can import calculate_average directly from my_package (thanks to __init__.py):

# my_package/__main__.py
 
from . import PACKAGE_VERSION, greet, calculate_average  # Relative import
# Or: from my_package import PACKAGE_VERSION, greet, calculate_average (absolute import)
 
def main():
    print(f"Running my_package v{PACKAGE_VERSION}")
    print(greet("Python Developer"))
    
    # Use the submodule function exposed via __init__.py
    scores = [90.5, 85.0, 95.5, 88.0]
    avg = calculate_average(scores)
    print(f"Average score: {avg:.2f}")
 
if __name__ == "__main__":
    main()

5. Common Pitfalls and Solutions#

ImportError: "No module named my_package"#

Problem: When running __main__.py directly (e.g., python my_package/__main__.py), Python may fail to recognize my_package as a package, causing ImportError: No module named 'my_package'.

Solution: Run the package as a module with the -m flag from the parent directory of my_package:

cd my_project  # Navigate to the root directory containing my_package
python -m my_package  # Runs my_package/__main__.py

This tells Python to treat my_package as a module, resolving import paths.

Circular Imports#

Problem: If __init__.py imports from __main__.py and vice versa, you’ll get a ImportError due to circular dependencies.

Solution: Restructure code to avoid circular imports. For example:

  • Move shared logic to a separate submodule (e.g., common.py).
  • Ensure dependencies flow in one direction (e.g., __init__.py imports from submodules, __main__.py imports from __init__.py, but not the other way around).

Running the Package Correctly with -m#

Always use python -m package_name to run the package. Avoid running __main__.py directly with python my_package/__main__.py, as this bypasses Python’s package resolution logic.

6. Best Practices#

  • Keep __init__.py Minimal: Use it only for initialization and exposing public API. Avoid heavy computations or complex logic here.
  • Use __all__ for Clarity: Define __all__ in __init__.py to document public exports. This helps users (and __main__.py) understand what’s intended for external use.
  • Prefer Absolute Imports for Installed Packages: If your package is installed (e.g., via pip), use absolute imports like from my_package import obj instead of relative imports.
  • Avoid Circular Dependencies: Structure code so that __init__.py and __main__.py do not import from each other.

7. Conclusion#

Accessing objects from __init__.py in __main__.py is straightforward once you understand Python’s package structure and import logic. By leveraging direct/relative imports, __all__ for API clarity, and the -m flag for execution, you can seamlessly share code between these critical files. Remember to keep __init__.py focused on initialization and public API exposure, and structure your project to avoid common pitfalls like circular imports.

8. References#