< Back

Modern Python Best Practices for Developers

MacBook Pro showing programming language

Welcome to the world of Python! Whether you're transitioning from Perl, Java, or another language, Python offers a clean and readable approach to software development. This guide covers essential best practices that will help you write better Python code from day one.

1. Follow PEP 8 Style Guide

PEP 8 is the official style guide for Python code. It covers everything from naming conventions to code layout. Key points:

Laptop showing coding program

2. Use Virtual Environments

Always use virtual environments for your projects. This keeps dependencies isolated:

# Using venv (built-in since Python 3.3)
python -m venv myproject_env
source myproject_env/bin/activate  # On Windows: .\myproject_env\Scripts\activate

# Or use poetry for modern dependency management
poetry new myproject
cd myproject
poetry add requests httpx

3. Leverage Type Hints

Python 3.5+ supports type hints. They improve code clarity and enable static analysis:

from typing import List, Optional

def process_users(user_ids: List[int], include_inactive: bool = False) -> List[dict]:
    """Process user records and return formatted data."""
    results: List[dict] = []
    for uid in user_ids:
        user = fetch_user(uid)
        if user or include_inactive:
            results.append(format_user(user))
    return results

4. Use F-Strings for Formatting

Introduced in Python 3.6, f-strings are the most readable way to format strings:

name = "Python"
version = 3.12

# Old way (still works but less readable)
print("Welcome to %s %.1f!" % (name, version))
print("Welcome to {0} {1:.1f}!".format(name, version))

# Modern way with f-strings
print(f"Welcome to {name} {version:.1f}!")  # Clean and readable!

# Even supports expressions
print(f"Next version: {version + 1:.1f}")

Code on computer screen

5. Exception Handling Best Practices

Handle exceptions specifically and never use bare except clauses:

# Good: Catch specific exceptions
from requests import RequestException, Timeout

try:
    response = requests.get(url, timeout=30)
    data = response.json()
except Timeout:
    logger.warning(f"Request to {url} timed out")
    raise ServiceUnavailable("External service timeout")
except RequestException as e:
    logger.error(f"Request failed: {e}")
    raise ServiceUnavailable(f"External service error: {e}")
except ValueError as e:
    # JSON parsing failed
    logger.error(f"Invalid JSON response: {e}")
    raise DataError("Invalid response format")

# Bad: Never do this
try:
    do_something()
except:  # Catches everything including KeyboardInterrupt!
    pass

6. Use List Comprehensions and Generators

Pythonic code often uses comprehensions for cleaner iteration:

# List comprehension - clean and efficient
squares = [x**2 for x in range(10) if x % 2 == 0]

# Dictionary comprehension
users_by_id = {user.id: user for user in user_list}

# Generator expression for memory efficiency (lazy evaluation)
total = sum(x**2 for x in range(1000000))  # No list created in memory!

7. Context Managers for Resource Management

Always use context managers (the with statement) for resources:

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

# Database connections
with sqlite3.connect('mydb.db') as conn:
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    # Connection automatically committed/closed

# Custom context managers with contextlib
from contextlib import contextmanager

@contextmanager
def managed_resource():
    resource = acquire_resource()
    try:
        yield resource
    finally:
        resource.release()

8. Documentation Strings

Follow PEP 257 for docstrings. Use triple double-quotes and document parameters:

def calculate_discount(price: float, customer_type: str, years_active: int) -> float:
    """Calculate discount percentage based on customer loyalty.

    Args:
        price: Original item price in GBP
        customer_type: 'regular', 'vip', or 'enterprise'
        years_active: Number of years as a customer

    Returns:
        Discounted price after applying appropriate percentage

    Raises:
        ValueError: If customer_type is not recognized
    """
    base_discount = {'regular': 0.05, 'vip': 0.15, 'enterprise': 0.25}

    if customer_type not in base_discount:
        raise ValueError(f"Unknown customer type: {customer_type}")

    loyalty_bonus = min(years_active * 0.01, 0.10)  # Max 10% loyalty bonus
    discount_rate = base_discount[customer_type] + loyalty_bonus

    return price * (1 - discount_rate)

9. Testing with pytest

Modern Python testing is dominated by pytest. It's powerful and simple:

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

def test_add_positive_numbers():
    assert add(2, 3) == 5

def test_add_negative_numbers():
    assert add(-1, -1) == -2

def test_divide_by_zero_raises():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

# Parametrized tests for multiple cases
@pytest.mark.parametrize("a,b,expected", [
    (2, 3, 5),
    (1, 1, 2),
    (0, 0, 0),
    (-1, 1, 0),
])
def test_add_various_cases(a, b, expected):
    assert add(a, b) == expected

10. Modern Tooling Recommendations

Set up these tools for a professional Python workflow:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black
        language_version: python3.12

  - repo: https://github.com/pycqa/isort
    rev: 5.13.2
    hooks:
      - id: isort

  - repo: https://github.com/pycqa/flake8
    rev: 6.1.0
    hooks:
      - id: flake8

Summary

Python's philosophy emphasizes readability and simplicity. By following these best practices, you'll write cleaner, more maintainable code that other developers (and your future self) will appreciate!

Key takeaways:

Happy coding! ๐Ÿ๐Ÿš€


< Back