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.