Typorithm · medium

Importable modules and the command-line boundary

pythonlibrariespackagingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Importable modules and the command-line boundary

0%

Concept and contract

Keep reusable definitions available without accidentally running the command-line action.

A reusable module should expose definitions without unexpectedly launching its command-line workflow. The conventional __name__ guard separates those responsibilities when a file may be used as a script and as a source of reusable functions. It does not make arbitrary top-level code disappear.

The fixture task.py defines report and prints only inside its main guard. The driver uses runpy with two explicit execution names to observe that boundary. This is a controlled namespace-execution experiment, not a simulation of every import-cache behavior: ordinary imports also involve module discovery and sys.modules caching.

Mechanism and worked example

The first run_path call uses owned_module as the namespace name and captures standard output.

The first run_path call uses owned_module as the namespace name and captures standard output. Report is available in the returned namespace, but the guarded print does not run. The second call uses __main__, so the same guard prints the report value.

Three separate fixture values make this distinction observable without hard-coding one answer. The driver returns the import-style output, script output and value from calling the reusable function. Capturing output is a test instrument here, not a recommendation to conceal unexpected messages in a production library.

Complete worked example

python

import contextlib
import io
import runpy

def inspect_example():
    imported_output = io.StringIO()
    with contextlib.redirect_stdout(imported_output):
        namespace = runpy.run_path('task.py', run_name='owned_module')
    script_output = io.StringIO()
    with contextlib.redirect_stdout(script_output):
        runpy.run_path('task.py', run_name='__main__')
    return {'importOutput': imported_output.getvalue(),
            'scriptOutput': script_output.getvalue(), 'value': namespace['report']()}
Reasoning
The first run_path call uses owned_module as the namespace name and captures standard output. Report is available in the returned namespace, but the guarded print does not run. The second call uses __main__, so the same guard prints the report value.
Download the reusable Python and packaging computer lab ↗

Counterexamples and common mistakes

The counterexample uses __main__ for the first execution too.

The counterexample uses __main__ for the first execution too. The reusable-namespace observation now prints, violating the empty-output contract. The function definition still exists, so checking only the returned calculation would miss the accidental action.

Moving the entire definition inside the main guard would create another defect: callers would no longer receive the intended reusable function. Keep library definitions outside the action boundary, and place only the command-line work behind it. A real import cycle deserves an explicit dependency refactor rather than hiding imports randomly until one invocation appears to succeed.

Controlled bug: the executable contract rejects this version

python

import contextlib
import io
import runpy

def inspect_example():
    imported_output = io.StringIO()
    with contextlib.redirect_stdout(imported_output):
        namespace = runpy.run_path('task.py', run_name='__main__')
    script_output = io.StringIO()
    with contextlib.redirect_stdout(script_output):
        runpy.run_path('task.py', run_name='__main__')
    return {'importOutput': imported_output.getvalue(),
            'scriptOutput': script_output.getvalue(), 'value': namespace['report']()}
Reasoning
The counterexample uses __main__ for the first execution too. The reusable-namespace observation now prints, violating the empty-output contract. The function definition still exists, so checking only the returned calculation would miss the accidental action.
Download the reusable Python and packaging computer lab ↗

Independent practice

Add moduleName to the report from the first returned namespace.

Add moduleName to the report from the first returned namespace. Preserve the two output observations and the function result. The first hint is that run_path returns the globals dictionary used for that execution, including its explicit __name__ value.

The second hint is to inspect the first namespace rather than the script's second execution. All three cases should report owned_module even when the numeric result differs. Do not replace this observation with the filename: execution name and filesystem location describe different aspects of the module boundary.

Step 1: Observe a module without executing its command-line block and add the imported namespace name to the report. Step 2: Run the accepted baseline, edit the exercise, then check its independent solution cases; preserve caller inputs and the documented exception behavior.

Practice starter: solve before reading the solution

python

import contextlib
import io
import runpy

def inspect_example():
    imported_output = io.StringIO()
    with contextlib.redirect_stdout(imported_output):
        namespace = runpy.run_path('task.py', run_name='owned_module')
    script_output = io.StringIO()
    with contextlib.redirect_stdout(script_output):
        runpy.run_path('task.py', run_name='__main__')
    return {'importOutput': imported_output.getvalue(),
            'scriptOutput': script_output.getvalue(), 'value': namespace['report']()}

# Task: Observe a module without executing its command-line block and add the imported namespace name to the report.
# Practice contract: Observe a module without executing its command-line block and add the imported namespace name to the report.
Reasoning
Observe a module without executing its command-line block and add the imported namespace name to the report.
Download the reusable Python and packaging computer lab ↗
  • Hint 1: Add moduleName to the report from the first returned namespace. Preserve the two output observations and the function result. The first hint is that run_path returns the globals dictionary used for that execution, including its explicit __name__ value.
  • Hint 2: The second hint is to inspect the first namespace rather than the script's second execution. All three cases should report owned_module even when the numeric result differs. Do not replace this observation with the filename: execution name and filesystem location describe different aspects of the module boundary.

Solution, tests and trade-offs

The solution reads the namespace's __name__ and keeps the other observations unchanged.

The solution reads the namespace's __name__ and keeps the other observations unchanged. Acceptance verifies different fixture values, so both namespace behavior and reusable calculation remain covered. The controlled bug proves that an apparently correct calculation can coexist with an unwanted execution side effect.

For a package CLI, a small __main__.py can delegate to an explicit main function, while reusable logic stays in another module. That structure avoids putting command parsing into every import. Ordinary import caching and reload behavior need separate tests if the application depends on them; this lesson deliberately establishes only the guarded execution boundary.

Practice solution and acceptance checks

python

import contextlib
import io
import runpy

def inspect_example():
    imported_output = io.StringIO()
    with contextlib.redirect_stdout(imported_output):
        namespace = runpy.run_path('task.py', run_name='owned_module')
    script_output = io.StringIO()
    with contextlib.redirect_stdout(script_output):
        runpy.run_path('task.py', run_name='__main__')
    return {'importOutput': imported_output.getvalue(),
            'scriptOutput': script_output.getvalue(), 'value': namespace['report'](), 'moduleName': namespace['__name__']}
Reasoning
The solution reads the namespace's __name__ and keeps the other observations unchanged. Acceptance verifies different fixture values, so both namespace behavior and reusable calculation remain covered. The controlled bug proves that an apparently correct calculation can coexist with an unwanted execution side effect.
Download the reusable Python and packaging computer lab ↗

Interview reasoning and next step

Explain why the filename does not determine which guard branch this driver executes.

Explain why the filename does not determine which guard branch this driver executes. Then distinguish defining report, calling report and printing its result. Identify the execution name used by each observed path, rather than treating every execution as a normal cached import.

For a stretch, create a tiny package with a __main__.py delegating to a reusable function and compare importing the package with running python -m package. Capture both outputs in a fresh process. Keep the fixture owned and deterministic so accidental startup behavior is visible without contacting a real service.

Optional stretch — after the core task works: For a stretch, create a tiny package with a __main__.py delegating to a reusable function and compare importing the package with running python -m package. Capture both outputs in a fresh process. Keep the fixture owned and deterministic so accidental startup behavior is visible without contacting a real service.

Computer practice: https://applydjinn.com/typorithm/python-engineering-lab-20261107.zip. Use an isolated Python 3.13 environment, install requirements.txt, then run verify.py. Edit exercises/pye-imports.py and run --practice pye-imports. Fixture files are created in fresh temporary directories for every case. Packaging checks build real local artifacts, install the exact wheel without an index and run outside the source tree. No package is published to PyPI. The app includes static examples, prepared results and questions; it does not run Python or provide an editor.

Keep reusable definitions available without accidentally running the command-line action.