Python Programming Best Practices
A Comprehensive Tutorial for Clean, Efficient Code
Version 3.12+ Compatible
1. Code Organization and Structure
Project Structure: Follow a consistent directory layout that separates concerns. Standard Python
project structure promotes maintainability and collaboration.
Recommended structure: project/ ■■■ src/ ■ ■■■ package/ ■ ■■■
__init__.py ■ ■■■ modules.py ■■■ tests/ ■ ■■■ test_modules.py ■■■
docs/ ■■■ requirements.txt ■■■ setup.py ■■■ README.md
Module Design Principles: • Single Responsibility: Each module should have one clear purpose •
High Cohesion: Related functionality stays together • Low Coupling: Minimize dependencies between
modules • Clear Interfaces: Well-defined public APIs with docstrings
2. PEP 8 Style Guide Essentials
Naming Conventions: • Classes: CapWords (PascalCase) - class DataProcessor •
Functions/Variables: snake_case - def calculate_total() • Constants: UPPER_SNAKE_CASE -
MAX_RETRY_COUNT = 3 • Private: Leading underscore - _internal_method()
Code Layout: • Maximum line length: 79 characters (docstrings: 72) • Indentation: 4 spaces (never mix
tabs and spaces) • Two blank lines between top-level definitions • One blank line between method
definitions • Imports ordered: standard library, third-party, local
3. Type Hints and Annotations
Type hints improve code readability and enable better IDE support and static analysis.
from typing import List, Dict, Optional, Union, Tuple from
dataclasses import dataclass def process_data( items: List[str],
config: Dict[str, Any], max_items: Optional[int] = None ) ->
Tuple[List[str], int]: """Process items according to config."""
processed = [] for item in items[:max_items]: if validate_item(item):
processed.append(transform(item)) return processed, len(processed)
@dataclass class User: name: str age: int email: Optional[str] = None
4. Exception Handling Best Practices
Principles: • Be specific with exception types • Don't silence exceptions without logging • Use context
managers for resource management • Create custom exceptions for domain-specific errors
class ValidationError(Exception): """Custom exception for validation
failures.""" pass def safe_divide(a: float, b: float) -> float:
"""Safely divide two numbers.""" try: return a / b except
ZeroDivisionError: logger.error(f"Division by zero: {a}/{b}") raise
ValidationError("Cannot divide by zero") except TypeError as e:
logger.error(f"Type error in division: {e}") raise
ValidationError(f"Invalid types: {type(a)}, {type(b)}") # Context
manager for file handling with open('data.txt', 'r') as file: data =
file.read() # Automatic cleanup
5. Performance Optimization
Common Optimization Techniques:
Technique Use Case Example
List Comprehension Faster than loops for simple transformations
[x*2 for x in range(100)]
Generator Expressions Memory-efficient for large datasets (x*2 for x in range(1000000))
Built-in Functions C-optimized implementations sum(), min(), max(), sorted()
Collections Module Specialized containers deque, Counter, defaultdict
Caching/Memoization Avoid redundant calculations @lru_cache(maxsize=128)
from functools import lru_cache from collections import Counter,
defaultdict import time # Memoization example @lru_cache(maxsize=128)
def fibonacci(n: int) -> int: if n < 2: return n return
fibonacci(n-1) + fibonacci(n-2) # Efficient counting words =
['apple', 'banana', 'apple', 'cherry', 'banana'] word_count =
Counter(words) # {'apple': 2, 'banana': 2, 'cherry': 1} # Default
values graph = defaultdict(list) graph['node1'].append('node2') # No
KeyError
6. Testing and Quality Assurance
Testing Pyramid: • Unit Tests: Test individual functions/methods (70%) • Integration Tests: Test
component interactions (20%) • End-to-End Tests: Test complete workflows (10%)
import pytest from unittest.mock import Mock, patch class
TestUserService: @pytest.fixture def user_service(self): return
UserService() def test_create_user_success(self, user_service): #
Arrange user_data = {"name": "Alice", "email": "
[email protected]"} #
Act user = user_service.create_user(**user_data) # Assert assert
user.name == "Alice" assert user.email == "
[email protected]"
@patch('requests.get') def test_fetch_user_data(self, mock_get): #
Mock external API mock_get.return_value.json.return_value = {"id": 1,
"name": "Bob"} result = fetch_user_from_api(1) assert result["name"]
== "Bob" mock_get.assert_called_once() # Parametrized testing
@pytest.mark.parametrize("input,expected", [ (2, 4), (3, 9), (4, 16),
]) def test_square(input, expected): assert square(input) == expected
7. Documentation Standards
Docstring Formats: Use Google, NumPy, or Sphinx style consistently. Document modules, classes,
methods, and complex functions.
def calculate_statistics( data: List[float], percentiles: List[int] =
None ) -> Dict[str, float]: """Calculate descriptive statistics for
numerical data. Args: data: List of numerical values to analyze
percentiles: Optional list of percentiles to calculate (0-100)
Returns: Dictionary containing: - mean: Average value - median:
Middle value - std: Standard deviation - percentiles: Dict of
requested percentiles Raises: ValueError: If data is empty or
contains non-numeric values Examples: >>> stats =
calculate_statistics([1, 2, 3, 4, 5]) >>> stats['mean'] 3.0 """ if
not data: raise ValueError("Data cannot be empty") # Implementation
here return statistics
8. Security Best Practices
Security Guidelines: • Never hardcode sensitive data (use environment variables) • Validate and
sanitize all user inputs • Use parameterized queries to prevent SQL injection • Keep dependencies
updated (use tools like pip-audit) • Use secrets module for cryptographic operations • Implement proper
authentication and authorization
import os import secrets from typing import Optional import hashlib #
Environment variables for sensitive data DATABASE_URL =
os.environ.get('DATABASE_URL') API_KEY = os.environ.get('API_KEY') #
Secure random token generation def generate_secure_token(length: int
= 32) -> str: return secrets.token_urlsafe(length) # Password hashing
(use bcrypt or argon2 in production) def hash_password(password: str,
salt: Optional[str] = None) -> str: if salt is None: salt =
secrets.token_hex(16) return hashlib.pbkdf2_hmac('sha256',
password.encode('utf-8'), salt.encode('utf-8'), 100000).hex() # Input
validation def validate_email(email: str) -> bool: import re pattern
= r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return
bool(re.match(pattern, email))
9. Design Patterns and Advanced Concepts
Common Python Patterns: • Singleton: Ensure single instance • Factory: Create objects without
specifying exact class • Decorator: Add functionality without modifying structure • Context Manager:
Resource management with 'with' statement • Observer: Event-driven programming
10. Code Review Checklist
Category Items to Check
Functionality ✓ Code works as intended
✓ Edge cases handled
✓ Error handling present
Style ✓ PEP 8 compliant
✓ Consistent naming
✓ No code duplication
Documentation ✓ Docstrings present
✓ Complex logic explained
✓ README updated
Testing ✓ Unit tests written
✓ Tests pass
✓ Good coverage (>80%)
Performance ✓ No obvious bottlenecks
✓ Efficient algorithms
✓ Memory usage considered
Security ✓ Input validation
✓ No hardcoded secrets
✓ Dependencies secure
Remember: Clean code is written for humans to read, not just for computers to execute!