Call Python Script From Another Python Script

5 min read

Calling a Python script from another Python script is a fundamental skill that transforms isolated code snippets into modular, maintainable applications. Whether you are building a data pipeline, automating system administration tasks, or structuring a large-scale project, understanding the nuances of script execution—specifically the differences between subprocess, import, and runpy—is critical for writing dependable software And that's really what it comes down to. And it works..

Why Modular Execution Matters

Before diving into the syntax, it helps to understand why developers choose one method over another. The decision usually hinges on three factors: isolation, data exchange, and dependency management Simple, but easy to overlook..

  • Isolation: Do you need the child script to run in a completely separate memory space? If the child script crashes, should it kill the parent process?
  • Data Exchange: Are you passing complex Python objects (dictionaries, custom classes, Pandas DataFrames) or just simple strings and exit codes?
  • Environment: Does the child script require a different virtual environment or Python interpreter version than the parent?

Answering these questions upfront saves hours of debugging later Easy to understand, harder to ignore..

Method 1: The subprocess Module (Standard Approach)

The subprocess module is the most versatile and widely used tool for spawning new processes. systemandos.Day to day, it replaces older modules like os. spawn, offering better control over input/output streams and error handling Simple, but easy to overlook..

Basic Execution with subprocess.run

Introduced in Python 3.5, subprocess.run is the recommended high-level API for most use cases. It waits for the command to complete and returns a CompletedProcess object.

import subprocess

try:
    result = subprocess.Think about it: run(
        ["python", "child_script. Here's the thing — py"], 
        capture_output=True, 
        text=True, 
        check=True
    )
    print("Standard Output:", result. In practice, stdout)
    print("Standard Error:", result. Think about it: stderr)
    print("Exit Code:", result. returncode)
except subprocess.Practically speaking, calledProcessError as e:
    print(f"Script failed with exit code {e. returncode}")
    print(f"Error output: {e.

**Key Parameters Explained:**
*   **`args` as a list:** Passing arguments as a list `["python", "script.py", "arg1"]` is safer and more portable than a single string. It avoids shell injection vulnerabilities and handles spaces in file paths automatically.
*   **`capture_output=True`:** A shortcut (Python 3.7+) for setting `stdout=subprocess.PIPE` and `stderr=subprocess.PIPE`.
*   **`text=True`:** Returns strings (decoded using system default encoding) instead of raw bytes. In Python 3.6 and earlier, use `universal_newlines=True`.
*   **`check=True`:** Raises a `CalledProcessError` exception if the exit code is non-zero, forcing explicit error handling.

### Handling Arguments and Data Passing

Since `subprocess` creates a distinct process, you cannot share memory directly. Communication happens via **Command Line Arguments**, **Standard Input (stdin)**, or **Files**.

**1. Command Line Arguments (Best for simple config):**
```python
# parent.py
user_id = 42
mode = "verbose"
subprocess.run(["python", "processor.py", str(user_id), mode])

# processor.py (child)
import sys
user_id = int(sys.argv[1])
mode = sys.argv[2]

2. Standard Input (Best for streaming data):

# parent.py
data_payload = "line1\nline2\nline3"
proc = subprocess.run(
    ["python", "consumer.py"], 
    input=data_payload, 
    text=True, 
    capture_output=True
)

# consumer.py (child)
import sys
for line in sys.stdin:
    print(f"Processing: {line.strip()}")

3. JSON via Stdout (Best for structured data return): This is a powerful pattern for returning complex results without writing temporary files Most people skip this — try not to..

# parent.py
import json, subprocess
result = subprocess.run(["python", "calculator.py", "10", "5"], capture_output=True, text=True)
if result.returncode == 0:
    data = json.loads(result.stdout)
    print(f"Sum: {data['sum']}, Product: {data['product']}")

# calculator.py
import sys, json
a, b = int(sys.argv[1]), int(sys.argv[2])
output = {"sum": a + b, "product": a * b}
print(json.dumps(output)) # Print ONLY json to stdout

Advanced: Asynchronous Execution with Popen

If your parent script needs to continue working while the child runs (e.Consider this: , a GUI application or a web server), use subprocess. g.Popen. It returns immediately without waiting.

import subprocess
import time

proc = subprocess.Popen(["python", "long_task.py"])

# Parent does other work here
print("Parent is free to do other tasks...")
time.sleep(2)

# Check status later
if proc.poll() is None:
    print("Child is still running...")
else:
    print(f"Child finished with code {proc.returncode}")

# Wait for completion if needed at the end
proc.wait()

Method 2: The import System (In-Process Execution)

If the child script is purely Python and you need high-performance data sharing (passing objects by reference), importing it as a module is superior. This runs the code in the same process and interpreter instance Not complicated — just consistent. Worth knowing..

Refactoring for Importability

A script designed to be run via CLI (if __name__ == "__main__":) often executes logic immediately upon import. To make it importable, wrap the entry point in a function Surprisingly effective..

utils/math_ops.py (Child):

def calculate_stats(numbers: list) -> dict:
    """Pure function logic, reusable and testable."""
    return {
        "mean": sum(numbers) / len(numbers),
        "max": max(numbers),
        "min": min(numbers)
    }

def main():
    # CLI entry point logic
    import sys, json
    nums = list(map(float, sys.argv[1:]))
    print(json.dumps(calculate_stats(nums)))

if __name__ == "__main__":
    main()

main_app.py (Parent):

# Import the function directly
from utils.math_ops import calculate_stats

dataset = [10, 20, 30, 40, 50]
stats = calculate_stats(dataset) # Direct function call, zero overhead
print(f"Mean: {stats['mean']}")

Dynamic Importing with importlib

Sometimes the module name isn't known until runtime (e., plugin systems). g.importlib handles this elegantly Most people skip this — try not to..

import importlib.util
import sys

def run_module_from_path(module_name, file_path):
    spec = importlib.util.spec_from_file_location(module_name, file_path)
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.

# Usage
my_script = run_module_from_path("dynamic_script", "/path/to/script.py")
my_script.main_function()

Caveats of Importing:

  1. Global State Pollution: Imports modify sys.modules. Running the same script twice via import won't re-execute the top-level code unless you importlib.reload().
  2. No Isolation: An unhandled exception in the child crashes the parent. A memory leak in the child affects the parent.
  3. Path Dependencies: The script's directory must be in sys.path or you must use spec_from_file_location.

Method 3:

Out This Week

Published Recently

Similar Ground

Keep Exploring

Thank you for reading about Call Python Script From Another Python Script. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home