← Python EnglishChapter 13 of 13

Best Practices

## Learning Objectives - Write clean, maintainable Python code - Follow PEP 8 style guidelines - Master debugging techniques - Write effective tests ## Code Style ### PEP 8 Guidelines - Use 4 spaces per indentation level - Lines should be ≤ 79 characters (soft limit) - Two blank lines between top-level definitions - One blank line between method definitions in a class - No spaces around `=` for keyword arguments or default values ```python # Good def greet(name, greeting="Hello"): return f"{greeting}, {name}!" # Bad def greet ( name, greeting = "Hello" ) : return greeting + ", " + name + "!" ``` ### Naming Conventions ```python # Variables and functions: snake_case user_name = "Alice" def calculate_total(): pass # Classes: PascalCase class UserAccount: pass # Constants: UPPER_SNAKE_CASE MAX_RETRIES = 3 API_URL = "https://api.example.com" # Private: prefix with underscore _private_function() _instance_variable ``` ### Imports ```python # Standard library first import os import sys # Third-party import requests from flask import Flask # Local application from mypackage import mymodule # Absolute vs Relative from package import module # Preferred from . import module # OK for relative import module # Avoid relative ``` ## Functions ### Single Responsibility ```python # Good - each function does one thing def validate_email(email): """Check if email is valid.""" return "@" in email and "." in email def send_email(recipient, message): """Send an email.""" if not validate_email(recipient): raise ValueError("Invalid email") # Send logic... # Bad - function does too much def send_email(recipient, message): # Validates, sends, logs, retries... ``` ### Early Returns ```python # Good - early return for guard clauses def process(user): if user is None: return None if not user.is_active: return None return user.process() # Bad - deeply nested def process(user): if user is not None: if user.is_active: return user.process() else: return None else: return None ``` ### Docstrings ```python def add(a, b): """ Add two numbers. Args: a: First number b: Second number Returns: The sum of a and b Raises: TypeError: If a or b is not a number """ if not isinstance(a, (int, float)) or not isinstance(b, (int, float)): raise TypeError("Arguments must be numbers") return a + b ``` ## Classes ### Use Properties ```python # Good - use property for controlled access class BankAccount: def __init__(self, balance): self._balance = balance @property def balance(self): return self._balance @balance.setter def balance(self, value): if value < 0: raise ValueError("Balance cannot be negative") self._balance = value ``` ### dataclasses for Simple Classes ```python from dataclasses import dataclass @dataclass class Point: x: float y: float def distance_from_origin(self): return (self.x ** 2 + self.y ** 2) ** 0.5 ``` ## Error Handling ### Specific Exceptions ```python # Good try: value = int(user_input) except ValueError: print("Please enter a valid number") # Bad - catches everything try: value = int(user_input) except: print("Error") ``` ### Exception Hierarchy ```python # Create specific exceptions class ValidationError(Exception): pass class InvalidEmailError(ValidationError): pass class InvalidPasswordError(ValidationError): pass ``` ### Don't Suppress Exceptions ```python # Bad try: do_something() except: pass # Good - at minimum, log it try: do_something() except Exception as e: logging.error(f"Failed: {e}") raise ``` ## Type Hints ```python from typing import List, Dict, Optional, Union def process(numbers: List[int]) -> Dict[str, int]: return { "count": len(numbers), "sum": sum(numbers), } def find_user(user_id: int) -> Optional[str]: if user_id == 0: return None return "User found" def parse(value: Union[str, int]) -> int: return int(value) ``` ## Performance ### List Comprehensions over Loops ```python # Good squares = [x ** 2 for x in range(100)] # Bad squares = [] for x in range(100): squares.append(x ** 2) ``` ### Generators for Large Data ```python # Good - memory efficient def read_large_file(filename): with open(filename) as f: for line in f: yield line # Bad - loads entire file def read_large_file(filename): with open(filename) as f: return f.readlines() ``` ### Local Variable Caching ```python import math # Slower - repeated attribute lookup def compute(numbers): result = [] for num in numbers: result.append(math.sin(num) + math.cos(num)) return result # Faster - cache math functions def compute(numbers): result = [] sin, cos = math.sin, math.cos for num in numbers: result.append(sin(num) + cos(num)) return result ``` ## Testing Basics ### Using unittest ```python import unittest class TestMath(unittest.TestCase): def test_add(self): self.assertEqual(1 + 1, 2) def test_divide(self): self.assertRaises(ZeroDivisionError, lambda: 1 / 0) if __name__ == "__main__": unittest.main() ``` ### Using pytest ```python import pytest def test_add(): assert 1 + 1 == 2 def test_divide(): with pytest.raises(ZeroDivisionError): 1 / 0 def test_list_comprehension(): result = [x ** 2 for x in range(5)] assert result == [0, 1, 4, 9, 16] ``` ## Debugging ### print() Debugging ```python def buggy_function(x): print(f"DEBUG: x = {x}") # Add debug output return x * 2 ``` ### assert ```python def process(age): assert age >= 0, "Age cannot be negative" assert isinstance(age, int), "Age must be an integer" # Process... ``` ### Using pdb ```python import pdb def buggy_function(x): pdb.set_trace() # Breakpoint return x * 2 ``` ## Summary - Follow PEP 8 style guidelines - Use meaningful variable and function names - Keep functions small and focused - Use type hints for documentation - Handle exceptions specifically - Use list comprehensions over loops - Use generators for large datasets - Write tests for critical code - Use assertions for debugging - Keep code DRY (Don't Repeat Yourself)

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →