Python - __name__ == '__main__' Explained

Python automatically sets the `__name__` variable for every module. When you run a Python file directly, Python assigns `'__main__'` to `__name__`. When you import that same file as a module,...

Key Insights

  • The if __name__ == "__main__" idiom prevents code from executing when a module is imported, running it only when the file is executed directly as a script
  • Python sets __name__ to "__main__" for the entry point script and to the module’s name (like "mymodule") when imported elsewhere
  • This pattern enables writing reusable modules that can function both as importable libraries and standalone executables

Understanding name Variable Behavior

Python automatically sets the __name__ variable for every module. When you run a Python file directly, Python assigns "__main__" to __name__. When you import that same file as a module, __name__ becomes the module’s actual name.

# demo.py
print(f"The value of __name__ is: {__name__}")

Running this directly:

$ python demo.py
The value of __name__ is: __main__

Importing it from another file:

# main.py
import demo
$ python main.py
The value of __name__ is: demo

This behavior provides a mechanism to detect how your code is being used and execute different logic accordingly.

The Basic Pattern

The standard idiom uses a conditional block at the bottom of your Python file:

# calculator.py
def add(a, b):
    return a + b

def subtract(a, b):
    return a - b

def multiply(a, b):
    return a * b

if __name__ == "__main__":
    # This code only runs when executed directly
    result = add(5, 3)
    print(f"5 + 3 = {result}")
    
    result = multiply(4, 7)
    print(f"4 * 7 = {result}")

When executed directly:

$ python calculator.py
5 + 3 = 8
4 * 7 = 28

When imported:

# app.py
from calculator import add, multiply

print(add(10, 20))  # 30
print(multiply(3, 4))  # 12
# The test code in calculator.py doesn't execute

Practical Use Case: Data Processing Script

Consider a data processing module that should work both as a library and a command-line tool:

# data_processor.py
import sys
import json

def load_data(filepath):
    """Load JSON data from file."""
    with open(filepath, 'r') as f:
        return json.load(f)

def calculate_average(data, field):
    """Calculate average of a numeric field."""
    values = [item[field] for item in data if field in item]
    return sum(values) / len(values) if values else 0

def filter_by_threshold(data, field, threshold):
    """Filter items where field exceeds threshold."""
    return [item for item in data if item.get(field, 0) > threshold]

def main():
    """Command-line interface."""
    if len(sys.argv) < 2:
        print("Usage: python data_processor.py <data_file.json>")
        sys.exit(1)
    
    filepath = sys.argv[1]
    data = load_data(filepath)
    
    avg_price = calculate_average(data, 'price')
    print(f"Average price: ${avg_price:.2f}")
    
    expensive_items = filter_by_threshold(data, 'price', 100)
    print(f"Items over $100: {len(expensive_items)}")

if __name__ == "__main__":
    main()

This module can be used in two ways:

As a command-line tool:

$ python data_processor.py products.json
Average price: $75.43
Items over $100: 12

As an imported library:

# analytics.py
from data_processor import load_data, calculate_average, filter_by_threshold

data = load_data('products.json')
avg = calculate_average(data, 'rating')
premium = filter_by_threshold(data, 'price', 500)

Testing and Development Benefits

The pattern enables embedding test code or examples directly in your module without affecting imports:

# string_utils.py
def reverse_words(text):
    """Reverse the order of words in a string."""
    return ' '.join(text.split()[::-1])

def title_case(text):
    """Convert text to title case."""
    return ' '.join(word.capitalize() for word in text.split())

def count_vowels(text):
    """Count vowels in text."""
    return sum(1 for char in text.lower() if char in 'aeiou')

if __name__ == "__main__":
    # Quick tests and usage examples
    test_string = "hello world python"
    
    print(f"Original: {test_string}")
    print(f"Reversed: {reverse_words(test_string)}")
    print(f"Title case: {title_case(test_string)}")
    print(f"Vowel count: {count_vowels(test_string)}")
    
    # Edge case testing
    assert reverse_words("a b c") == "c b a"
    assert count_vowels("xyz") == 0
    assert title_case("") == ""
    print("\nAll tests passed!")

Run directly for quick validation:

$ python string_utils.py
Original: hello world python
Reversed: python world hello
Title case: Hello World Python
Vowel count: 5

All tests passed!

Package Entry Points

When building packages, this pattern defines clear entry points:

# mypackage/cli.py
import argparse
from .core import process_file, validate_input
from .utils import setup_logging

def parse_arguments():
    parser = argparse.ArgumentParser(description='Data processing tool')
    parser.add_argument('input', help='Input file path')
    parser.add_argument('--verbose', action='store_true', help='Verbose output')
    parser.add_argument('--output', help='Output file path')
    return parser.parse_args()

def run_cli():
    """Main CLI execution function."""
    args = parse_arguments()
    
    if args.verbose:
        setup_logging('DEBUG')
    
    if not validate_input(args.input):
        print(f"Error: Invalid input file {args.input}")
        return 1
    
    result = process_file(args.input)
    
    if args.output:
        with open(args.output, 'w') as f:
            f.write(result)
    else:
        print(result)
    
    return 0

if __name__ == "__main__":
    exit(run_cli())

This allows running the package module directly:

$ python -m mypackage.cli input.txt --verbose --output result.txt

Common Anti-Pattern: Bare Code Execution

Avoid placing executable code at the module level without the guard:

# bad_example.py - DON'T DO THIS
import requests

def fetch_data(url):
    return requests.get(url).json()

# This runs on import!
data = fetch_data("https://api.example.com/data")
print(f"Fetched {len(data)} items")

When another module imports bad_example, it immediately makes an HTTP request. This creates:

  • Unexpected side effects during imports
  • Slower import times
  • Difficult-to-test code
  • Potential errors if the API is unavailable

The correct approach:

# good_example.py
import requests

def fetch_data(url):
    return requests.get(url).json()

if __name__ == "__main__":
    data = fetch_data("https://api.example.com/data")
    print(f"Fetched {len(data)} items")

Integration with Setup Tools

For distributable packages, combine this pattern with setup.py or pyproject.toml entry points:

# myapp/main.py
def main():
    """Application entry point."""
    print("Running myapp...")
    # Application logic here

if __name__ == "__main__":
    main()

In setup.py:

from setuptools import setup

setup(
    name='myapp',
    entry_points={
        'console_scripts': [
            'myapp=myapp.main:main',
        ],
    },
)

After installation, users can run myapp directly from the command line, while the module remains importable for programmatic use.

Performance Considerations

The if __name__ == "__main__" check has negligible performance impact. Python evaluates it once during module loading. However, be mindful of what you place inside the block. Expensive imports should go inside if they’re only needed for script execution:

# optimization.py
def core_function():
    """Lightweight function used by importers."""
    return "result"

if __name__ == "__main__":
    # Heavy imports only when running as script
    import matplotlib.pyplot as plt
    import pandas as pd
    import numpy as np
    
    # Script-specific code
    data = pd.read_csv('large_file.csv')
    # ... processing

This keeps imports fast for users who only need core_function() and don’t require the heavy dependencies.

Liked this? There's more.

Every week: one practical technique, explained simply, with code you can use immediately.