Claude Agent Skill · by Affaan M

Python Patterns

The python-patterns skill provides developers with idiomatic Python conventions, type hints, and best practices for writing robust, maintainable code aligned wi

Install
Terminal · npx
$npx skills add https://github.com/affaan-m/everything-claude-code --skill python-patterns
Works with Paperclip

How Python Patterns fits into a Paperclip company.

Python Patterns drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md750 lines
Expand
---name: python-patternsdescription: Pythonic idioms, PEP 8 standards, type hints, and best practices for building robust, efficient, and maintainable Python applications.origin: ECC--- # Python Development Patterns Idiomatic Python patterns and best practices for building robust, efficient, and maintainable applications. ## When to Activate - Writing new Python code- Reviewing Python code- Refactoring existing Python code- Designing Python packages/modules ## Core Principles ### 1. Readability Counts Python prioritizes readability. Code should be obvious and easy to understand. ```python# Good: Clear and readabledef get_active_users(users: list[User]) -> list[User]:    """Return only active users from the provided list."""    return [user for user in users if user.is_active]  # Bad: Clever but confusingdef get_active_users(u):    return [x for x in u if x.a]``` ### 2. Explicit is Better Than Implicit Avoid magic; be clear about what your code does. ```python# Good: Explicit configurationimport logging logging.basicConfig(    level=logging.INFO,    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') # Bad: Hidden side effectsimport some_modulesome_module.setup()  # What does this do?``` ### 3. EAFP - Easier to Ask Forgiveness Than Permission Python prefers exception handling over checking conditions. ```python# Good: EAFP styledef get_value(dictionary: dict, key: str) -> Any:    try:        return dictionary[key]    except KeyError:        return default_value # Bad: LBYL (Look Before You Leap) styledef get_value(dictionary: dict, key: str) -> Any:    if key in dictionary:        return dictionary[key]    else:        return default_value``` ## Type Hints ### Basic Type Annotations ```pythonfrom typing import Optional, List, Dict, Any def process_user(    user_id: str,    data: Dict[str, Any],    active: bool = True) -> Optional[User]:    """Process a user and return the updated User or None."""    if not active:        return None    return User(user_id, data)``` ### Modern Type Hints (Python 3.9+) ```python# Python 3.9+ - Use built-in typesdef process_items(items: list[str]) -> dict[str, int]:    return {item: len(item) for item in items} # Python 3.8 and earlier - Use typing modulefrom typing import List, Dict def process_items(items: List[str]) -> Dict[str, int]:    return {item: len(item) for item in items}``` ### Type Aliases and TypeVar ```pythonfrom typing import TypeVar, Union # Type alias for complex typesJSON = Union[dict[str, Any], list[Any], str, int, float, bool, None] def parse_json(data: str) -> JSON:    return json.loads(data) # Generic typesT = TypeVar('T') def first(items: list[T]) -> T | None:    """Return the first item or None if list is empty."""    return items[0] if items else None``` ### Protocol-Based Duck Typing ```pythonfrom typing import Protocol class Renderable(Protocol):    def render(self) -> str:        """Render the object to a string.""" def render_all(items: list[Renderable]) -> str:    """Render all items that implement the Renderable protocol."""    return "\n".join(item.render() for item in items)``` ## Error Handling Patterns ### Specific Exception Handling ```python# Good: Catch specific exceptionsdef load_config(path: str) -> Config:    try:        with open(path) as f:            return Config.from_json(f.read())    except FileNotFoundError as e:        raise ConfigError(f"Config file not found: {path}") from e    except json.JSONDecodeError as e:        raise ConfigError(f"Invalid JSON in config: {path}") from e # Bad: Bare exceptdef load_config(path: str) -> Config:    try:        with open(path) as f:            return Config.from_json(f.read())    except:        return None  # Silent failure!``` ### Exception Chaining ```pythondef process_data(data: str) -> Result:    try:        parsed = json.loads(data)    except json.JSONDecodeError as e:        # Chain exceptions to preserve the traceback        raise ValueError(f"Failed to parse data: {data}") from e``` ### Custom Exception Hierarchy ```pythonclass AppError(Exception):    """Base exception for all application errors."""    pass class ValidationError(AppError):    """Raised when input validation fails."""    pass class NotFoundError(AppError):    """Raised when a requested resource is not found."""    pass # Usagedef get_user(user_id: str) -> User:    user = db.find_user(user_id)    if not user:        raise NotFoundError(f"User not found: {user_id}")    return user``` ## Context Managers ### Resource Management ```python# Good: Using context managersdef process_file(path: str) -> str:    with open(path, 'r') as f:        return f.read() # Bad: Manual resource managementdef process_file(path: str) -> str:    f = open(path, 'r')    try:        return f.read()    finally:        f.close()``` ### Custom Context Managers ```pythonfrom contextlib import contextmanager @contextmanagerdef timer(name: str):    """Context manager to time a block of code."""    start = time.perf_counter()    yield    elapsed = time.perf_counter() - start    print(f"{name} took {elapsed:.4f} seconds") # Usagewith timer("data processing"):    process_large_dataset()``` ### Context Manager Classes ```pythonclass DatabaseTransaction:    def __init__(self, connection):        self.connection = connection     def __enter__(self):        self.connection.begin_transaction()        return self     def __exit__(self, exc_type, exc_val, exc_tb):        if exc_type is None:            self.connection.commit()        else:            self.connection.rollback()        return False  # Don't suppress exceptions # Usagewith DatabaseTransaction(conn):    user = conn.create_user(user_data)    conn.create_profile(user.id, profile_data)``` ## Comprehensions and Generators ### List Comprehensions ```python# Good: List comprehension for simple transformationsnames = [user.name for user in users if user.is_active] # Bad: Manual loopnames = []for user in users:    if user.is_active:        names.append(user.name) # Complex comprehensions should be expanded# Bad: Too complexresult = [x * 2 for x in items if x > 0 if x % 2 == 0] # Good: Use a generator functiondef filter_and_transform(items: Iterable[int]) -> list[int]:    result = []    for x in items:        if x > 0 and x % 2 == 0:            result.append(x * 2)    return result``` ### Generator Expressions ```python# Good: Generator for lazy evaluationtotal = sum(x * x for x in range(1_000_000)) # Bad: Creates large intermediate listtotal = sum([x * x for x in range(1_000_000)])``` ### Generator Functions ```pythondef read_large_file(path: str) -> Iterator[str]:    """Read a large file line by line."""    with open(path) as f:        for line in f:            yield line.strip() # Usagefor line in read_large_file("huge.txt"):    process(line)``` ## Data Classes and Named Tuples ### Data Classes ```pythonfrom dataclasses import dataclass, fieldfrom datetime import datetime @dataclassclass User:    """User entity with automatic __init__, __repr__, and __eq__."""    id: str    name: str    email: str    created_at: datetime = field(default_factory=datetime.now)    is_active: bool = True # Usageuser = User(    id="123",    name="Alice",    email="alice@example.com")``` ### Data Classes with Validation ```python@dataclassclass User:    email: str    age: int     def __post_init__(self):        # Validate email format        if "@" not in self.email:            raise ValueError(f"Invalid email: {self.email}")        # Validate age range        if self.age < 0 or self.age > 150:            raise ValueError(f"Invalid age: {self.age}")``` ### Named Tuples ```pythonfrom typing import NamedTuple class Point(NamedTuple):    """Immutable 2D point."""    x: float    y: float     def distance(self, other: 'Point') -> float:        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5 # Usagep1 = Point(0, 0)p2 = Point(3, 4)print(p1.distance(p2))  # 5.0``` ## Decorators ### Function Decorators ```pythonimport functoolsimport time def timer(func: Callable) -> Callable:    """Decorator to time function execution."""    @functools.wraps(func)    def wrapper(*args, **kwargs):        start = time.perf_counter()        result = func(*args, **kwargs)        elapsed = time.perf_counter() - start        print(f"{func.__name__} took {elapsed:.4f}s")        return result    return wrapper @timerdef slow_function():    time.sleep(1) # slow_function() prints: slow_function took 1.0012s``` ### Parameterized Decorators ```pythondef repeat(times: int):    """Decorator to repeat a function multiple times."""    def decorator(func: Callable) -> Callable:        @functools.wraps(func)        def wrapper(*args, **kwargs):            results = []            for _ in range(times):                results.append(func(*args, **kwargs))            return results        return wrapper    return decorator @repeat(times=3)def greet(name: str) -> str:    return f"Hello, {name}!" # greet("Alice") returns ["Hello, Alice!", "Hello, Alice!", "Hello, Alice!"]``` ### Class-Based Decorators ```pythonclass CountCalls:    """Decorator that counts how many times a function is called."""    def __init__(self, func: Callable):        functools.update_wrapper(self, func)        self.func = func        self.count = 0     def __call__(self, *args, **kwargs):        self.count += 1        print(f"{self.func.__name__} has been called {self.count} times")        return self.func(*args, **kwargs) @CountCallsdef process():    pass # Each call to process() prints the call count``` ## Concurrency Patterns ### Threading for I/O-Bound Tasks ```pythonimport concurrent.futuresimport threading def fetch_url(url: str) -> str:    """Fetch a URL (I/O-bound operation)."""    import urllib.request    with urllib.request.urlopen(url) as response:        return response.read().decode() def fetch_all_urls(urls: list[str]) -> dict[str, str]:    """Fetch multiple URLs concurrently using threads."""    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:        future_to_url = {executor.submit(fetch_url, url): url for url in urls}        results = {}        for future in concurrent.futures.as_completed(future_to_url):            url = future_to_url[future]            try:                results[url] = future.result()            except Exception as e:                results[url] = f"Error: {e}"    return results``` ### Multiprocessing for CPU-Bound Tasks ```pythondef process_data(data: list[int]) -> int:    """CPU-intensive computation."""    return sum(x ** 2 for x in data) def process_all(datasets: list[list[int]]) -> list[int]:    """Process multiple datasets using multiple processes."""    with concurrent.futures.ProcessPoolExecutor() as executor:        results = list(executor.map(process_data, datasets))    return results``` ### Async/Await for Concurrent I/O ```pythonimport asyncio async def fetch_async(url: str) -> str:    """Fetch a URL asynchronously."""    import aiohttp    async with aiohttp.ClientSession() as session:        async with session.get(url) as response:            return await response.text() async def fetch_all(urls: list[str]) -> dict[str, str]:    """Fetch multiple URLs concurrently."""    tasks = [fetch_async(url) for url in urls]    results = await asyncio.gather(*tasks, return_exceptions=True)    return dict(zip(urls, results))``` ## Package Organization ### Standard Project Layout ```myproject/├── src/│   └── mypackage/│       ├── __init__.py│       ├── main.py│       ├── api/│       │   ├── __init__.py│       │   └── routes.py│       ├── models/│       │   ├── __init__.py│       │   └── user.py│       └── utils/│           ├── __init__.py│           └── helpers.py├── tests/│   ├── __init__.py│   ├── conftest.py│   ├── test_api.py│   └── test_models.py├── pyproject.toml├── README.md└── .gitignore``` ### Import Conventions ```python# Good: Import order - stdlib, third-party, localimport osimport sysfrom pathlib import Path import requestsfrom fastapi import FastAPI from mypackage.models import Userfrom mypackage.utils import format_name # Good: Use isort for automatic import sorting# pip install isort``` ### __init__.py for Package Exports ```python# mypackage/__init__.py"""mypackage - A sample Python package.""" __version__ = "1.0.0" # Export main classes/functions at package levelfrom mypackage.models import User, Postfrom mypackage.utils import format_name __all__ = ["User", "Post", "format_name"]``` ## Memory and Performance ### Using __slots__ for Memory Efficiency ```python# Bad: Regular class uses __dict__ (more memory)class Point:    def __init__(self, x: float, y: float):        self.x = x        self.y = y # Good: __slots__ reduces memory usageclass Point:    __slots__ = ['x', 'y']     def __init__(self, x: float, y: float):        self.x = x        self.y = y``` ### Generator for Large Data ```python# Bad: Returns full list in memorydef read_lines(path: str) -> list[str]:    with open(path) as f:        return [line.strip() for line in f] # Good: Yields lines one at a timedef read_lines(path: str) -> Iterator[str]:    with open(path) as f:        for line in f:            yield line.strip()``` ### Avoid String Concatenation in Loops ```python# Bad: O(n²) due to string immutabilityresult = ""for item in items:    result += str(item) # Good: O(n) using joinresult = "".join(str(item) for item in items) # Good: Using StringIO for buildingfrom io import StringIO buffer = StringIO()for item in items:    buffer.write(str(item))result = buffer.getvalue()``` ## Python Tooling Integration ### Essential Commands ```bash# Code formattingblack .isort . # Lintingruff check .pylint mypackage/ # Type checkingmypy . # Testingpytest --cov=mypackage --cov-report=html # Security scanningbandit -r . # Dependency managementpip-auditsafety check``` ### pyproject.toml Configuration ```toml[project]name = "mypackage"version = "1.0.0"requires-python = ">=3.9"dependencies = [    "requests>=2.31.0",    "pydantic>=2.0.0",] [project.optional-dependencies]dev = [    "pytest>=7.4.0",    "pytest-cov>=4.1.0",    "black>=23.0.0",    "ruff>=0.1.0",    "mypy>=1.5.0",] [tool.black]line-length = 88target-version = ['py39'] [tool.ruff]line-length = 88select = ["E", "F", "I", "N", "W"] [tool.mypy]python_version = "3.9"warn_return_any = truewarn_unused_configs = truedisallow_untyped_defs = true [tool.pytest.ini_options]testpaths = ["tests"]addopts = "--cov=mypackage --cov-report=term-missing"``` ## Quick Reference: Python Idioms | Idiom | Description ||-------|-------------|| EAFP | Easier to Ask Forgiveness than Permission || Context managers | Use `with` for resource management || List comprehensions | For simple transformations || Generators | For lazy evaluation and large datasets || Type hints | Annotate function signatures || Dataclasses | For data containers with auto-generated methods || `__slots__` | For memory optimization || f-strings | For string formatting (Python 3.6+) || `pathlib.Path` | For path operations (Python 3.4+) || `enumerate` | For index-element pairs in loops | ## Anti-Patterns to Avoid ```python# Bad: Mutable default argumentsdef append_to(item, items=[]):    items.append(item)    return items # Good: Use None and create new listdef append_to(item, items=None):    if items is None:        items = []    items.append(item)    return items # Bad: Checking type with type()if type(obj) == list:    process(obj) # Good: Use isinstanceif isinstance(obj, list):    process(obj) # Bad: Comparing to None with ==if value == None:    process() # Good: Use isif value is None:    process() # Bad: from module import *from os.path import * # Good: Explicit importsfrom os.path import join, exists # Bad: Bare excepttry:    risky_operation()except:    pass # Good: Specific exceptiontry:    risky_operation()except SpecificError as e:    logger.error(f"Operation failed: {e}")``` __Remember__: Python code should be readable, explicit, and follow the principle of least surprise. When in doubt, prioritize clarity over cleverness.