Toggle Menu Icon
Working with Python

Python Idioms

Python has many idiomatic patterns that make code more readable, maintainable, and “Pythonic.” Here are the essential idioms every new Python developer should know.

The if __name__ == "__main__" Pattern

This is one of the most important Python idioms you’ll encounter:

def main():
    print("This script is being run directly")
    # Your main program logic here

if __name__ == "__main__":
    main()

Why Use This Pattern?

When Python runs a file, it sets the __name__ variable:

This pattern allows your script to:

  1. Be both a script and a module - Code can be imported without executing the main logic
  2. Prevent unwanted execution - When someone imports your module, the main code won’t run
  3. Enable testing - You can import functions for testing without side effects

Example:

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

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

def main():
    print("Calculator running...")
    result = add(5, 3)
    print(f"5 + 3 = {result}")

if __name__ == "__main__":
    main()

Now you can either run it directly (python calculator.py) or import it (from calculator import add).

Setting Up a Proper Python Environment

Use Virtual Environments

Always use virtual environments to isolate project dependencies:

# Create a virtual environment
python -m venv myproject_env

# Activate it (macOS/Linux)
source myproject_env/bin/activate

# Activate it (Windows)
myproject_env\Scripts\activate

# Install packages
pip install requests pandas

# Save dependencies
pip freeze > requirements.txt

# Deactivate when done
deactivate

Project Structure

Organize your projects with a standard structure:

myproject/
├── README.md
├── requirements.txt
├── setup.py (optional)
├── src/
│   └── myproject/
│       ├── __init__.py
│       ├── main.py
│       └── utils.py
├── tests/
│   ├── __init__.py
│   └── test_main.py
└── docs/

Essential Python Idioms

List Comprehensions

Replace verbose loops with concise list comprehensions:

# Instead of this:
squares = []
for i in range(10):
    squares.append(i ** 2)

# Use this:
squares = [i ** 2 for i in range(10)]

# With conditions:
even_squares = [i ** 2 for i in range(10) if i % 2 == 0]

Dictionary Comprehensions

Create dictionaries efficiently:

# Create a dictionary from two lists
keys = ['a', 'b', 'c']
values = [1, 2, 3]
my_dict = {k: v for k, v in zip(keys, values)}

# Transform existing dictionary
prices = {'apple': 0.50, 'banana': 0.30, 'orange': 0.75}
expensive = {fruit: price for fruit, price in prices.items() if price > 0.40}

Enumerate Instead of Range(len())

items = ['apple', 'banana', 'cherry']

# Instead of this:
for i in range(len(items)):
    print(f"{i}: {items[i]}")

# Use this:
for i, item in enumerate(items):
    print(f"{i}: {item}")

Zip for Parallel Iteration

names = ['Alice', 'Bob', 'Charlie']
ages = [25, 30, 35]

# Instead of indexing:
for i in range(len(names)):
    print(f"{names[i]} is {ages[i]} years old")

# Use zip:
for name, age in zip(names, ages):
    print(f"{name} is {age} years old")

Context Managers (with statements)

Always use context managers for resource management:

# File handling
with open('file.txt', 'r') as f:
    content = f.read()
# File automatically closed

# Custom context managers
from contextlib import contextmanager

@contextmanager
def timer():
    import time
    start = time.time()
    yield
    end = time.time()
    print(f"Elapsed: {end - start:.2f} seconds")

with timer():
    # Your code here
    time.sleep(1)

String Formatting

Use f-strings (Python 3.6+) for readable string formatting, see string formatting cookbook for more:

name = "Alice"
age = 30

# Old ways (avoid):
message = "Hello, %s! You are %d years old." % (name, age)
message = "Hello, {}! You are {} years old.".format(name, age)

# Modern way:
message = f"Hello, {name}! You are {age} years old."

# With expressions:
price = 19.99
message = f"Total: ${price * 1.08:.2f}"  # Includes tax, 2 decimal places

Default Dictionary Values

Use dict.get() or collections.defaultdict:

# Instead of checking if key exists:
if 'key' in my_dict:
    value = my_dict['key']
else:
    value = 'default'

# Use get():
value = my_dict.get('key', 'default')

# For counting or grouping:
from collections import defaultdict

# Count items
counter = defaultdict(int)
for item in items:
    counter[item] += 1

# Group items
groups = defaultdict(list)
for item in items:
    groups[item.category].append(item)

Unpacking and Multiple Assignment

# Tuple unpacking
point = (3, 4)
x, y = point

# Swap variables
a, b = b, a

# Extended unpacking (Python 3+)
first, *middle, last = [1, 2, 3, 4, 5]
# first = 1, middle = [2, 3, 4], last = 5

# Function arguments
def greet(name, age, city):
    return f"Hi {name}, {age} years old from {city}"

person_data = ("Alice", 30, "New York")
message = greet(*person_data)  # Unpacking tuple

person_dict = {"name": "Bob", "age": 25, "city": "Boston"}
message = greet(**person_dict)  # Unpacking dictionary

Exception Handling Best Practices

# Be specific with exceptions
try:
    value = int(user_input)
except ValueError:
    print("Please enter a valid number")
except KeyboardInterrupt:
    print("Operation cancelled")

# Use else and finally
try:
    file = open('data.txt')
except FileNotFoundError:
    print("File not found")
else:
    # Runs only if no exception occurred
    data = file.read()
finally:
    # Always runs
    if 'file' in locals():
        file.close()

# EAFP (Easier to Ask for Forgiveness than Permission)
try:
    return my_dict[key]
except KeyError:
    return default_value

Generator Expressions and Functions

Use generators for memory-efficient iteration:

# Generator expression (like list comprehension but lazy)
squares_gen = (i ** 2 for i in range(1000000))  # Memory efficient

# Generator function
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# Use generators
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]

Pathlib for File Operations

Use pathlib instead of os.path, see files article for more:

from pathlib import Path

# Instead of os.path
import os
file_path = os.path.join('data', 'files', 'input.txt')
if os.path.exists(file_path):
    with open(file_path, 'r') as f:
        content = f.read()

# Use pathlib
file_path = Path('data') / 'files' / 'input.txt'
if file_path.exists():
    content = file_path.read_text()

# More pathlib examples
current_dir = Path.cwd()
parent_dir = Path(__file__).parent
all_py_files = list(Path('.').glob('**/*.py'))

Type Hints (Python 3.5+)

Add type hints for better code documentation and IDE support, see type hints for more:

from typing import List, Dict, Optional, Union

def process_items(items: List[str], multiplier: int = 1) -> List[str]:
    """Process a list of items."""
    return [item * multiplier for item in items]

def get_user_data(user_id: int) -> Optional[Dict[str, Union[str, int]]]:
    """Get user data, returns None if not found."""
    # Implementation here
    pass

Truthy and Falsy Values

Understand how Python evaluates values in a boolean context:

# Falsy values:
# - None
# - False
# - Zero of any numeric type (0, 0.0, 0j)
# - Empty sequences (list, tuple, string: [], (), '')
# - Empty mappings (dictionary: {})
# - Instances of user-defined classes, if the class defines a __bool__() or __len__() method that returns False or 0.

my_list = []
if not my_list:  # More Pythonic than if len(my_list) == 0:
    print("List is empty")

my_string = "Hello"
if my_string:  # True because it's not empty
    print(f"String: {my_string}")

Iterable Slicing

Work with parts of sequences efficiently:

numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

first_three = numbers[:3]  # [0, 1, 2]
last_three = numbers[-3:] # [7, 8, 9]
middle = numbers[2:7]   # [2, 3, 4, 5, 6]
every_other = numbers[::2] # [0, 2, 4, 6, 8]
reverse_list = numbers[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

# Slicing works on strings too
text = "Pythonic"
print(text[1:4]) # yth

Use Underscore as a Throwaway Variable

Use _ for variables whose values you don’t intend to use:

# When unpacking, if you only need certain values
coordinates = (10, 20, 5)
x, _, z = coordinates # y is ignored

# In loops where the index or value is not needed
for _ in range(5):
    print("Hello")

Use any() and all()

Check for conditions in iterables concisely:

numbers = [1, 2, 3, -5, 6]

# Check if any number is negative
has_negative = any(n < 0 for n in numbers)
print(f"Has negative numbers: {has_negative}") # True

# Check if all numbers are positive
all_positive = all(n > 0 for n in numbers)
print(f"All numbers positive: {all_positive}") # False

Code Style and Standards

Follow PEP 8

Use tools to enforce Python’s style guide:

# Install formatting and linting tools
pip install black flake8 isort mypy

# Format code
black your_script.py

# Sort imports
isort your_script.py

# Check style
flake8 your_script.py

# Type checking
mypy your_script.py

Naming Conventions

# Variables and functions: snake_case
user_name = "Alice"
def calculate_total_price():
    pass

# Constants: UPPER_SNAKE_CASE
MAX_CONNECTIONS = 100
API_BASE_URL = "https://api.example.com"

# Classes: PascalCase
class UserAccount:
    pass

# Private attributes: leading underscore
class MyClass:
    def __init__(self):
        self._private_var = "internal use"
        self.__very_private = "name mangled"

Documentation

Write clear docstrings:

def calculate_area(length: float, width: float) -> float:
    """Calculate the area of a rectangle.

    Args:
        length: The length of the rectangle
        width: The width of the rectangle

    Returns:
        The area of the rectangle

    Raises:
        ValueError: If length or width is negative
    """
    if length < 0 or width < 0:
        raise ValueError("Length and width must be non-negative")
    return length * width

Testing Your Code

Write tests using pytest:

# test_calculator.py
import pytest
from calculator import add, subtract

def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0

def test_subtract():
    assert subtract(5, 3) == 2
    assert subtract(0, 5) == -5

def test_add_with_floats():
    assert add(0.1, 0.2) == pytest.approx(0.3)

# Run tests
# pytest test_calculator.py

References