Guide for writing clean, efficient, idiomatic Python 3.11+ code. Enforces type hints, Pydantic v2 for APIs, comprehensions over loops, EAFP error handling. Triggers on "python", "pythonic", ".py file", "write python", "python script", "python function", "python class", "pydantic", "fastapi", "pytest", "type hint", "typing", "dataclass", "async def", "asyncio", "aiohttp", "comprehension", "generator", "decorator", "context manager", "with statement", "exception handling", "try except", "raise", "logging python", "argparse", "click", "typer", "__init__", "__main__", "import", "from import", "python module", "python package", "requirements.txt", "pyproject.toml", "ruff", "mypy", "black", "isort", "python testing", "fixture", "parametrize", "edit .py", "modify .py", "update .py", "change .py", "fix .py", "refactor .py", "edit python", "modify python", "update python", "change python", "fix python code". PROACTIVE: MUST invoke BEFORE using Write OR Edit on ANY .py file.
Target: Python 3.11+ with modern tooling (uv, Pydantic v2, type hints).
Detailed patterns: See references/pydantic-patterns.md and references/advanced-patterns.md
| Pattern | Pythonic Way | Avoid |
|---------|--------------|-------|
| Iteration | for item in items: | for i in range(len(items)): |
| Index + Value | for i, v in enumerate(seq): | Manual counter |
| Dict Access | d.get("key", default) | if "key" in d: d["key"] |
| Dict Iteration | for k, v in d.items(): | for k in d: v = d[k] |
| Swap Variables | a, b = b, a | temp = a; a = b; b = temp |
| Build Strings | "".join(parts) | s += part in loop |
| Membership | x in set_or_dict | x in list (for large) |
| File I/O | with open(...) as f: | Manual f.close() |
| Truthiness | if items: | if len(items) > 0: |
| None Check | if x is None: | if x == None: |
Before EVERY Write or Edit tool call on a .py file:
╔══════════════════════════════════════════════════════════════════╗
║ 🛑 STOP - PYTHON SKILL CHECK ║
║ ║
║ You are about to modify a .py file. ║
║ ║
║ QUESTION: Is /python skill currently active? ║
║ ║
║ If YES → Proceed with the edit ║
║ If NO → STOP! Invoke /python FIRST, then edit ║
║ ║
║ This check applies to: ║
║ ✗ Write tool with file_path ending in .py ║
║ ✗ Edit tool with file_path ending in .py ║
║ ✗ ANY Python file, regardless of conversation topic ║
║ ║
║ Examples that REQUIRE this skill: ║
║ - "update the schemas" (edits schemas.py) ║
║ - "fix the import" (edits any .py file) ║
║ - "add logging" (edits Python code) ║
╚══════════════════════════════════════════════════════════════════╝
Why this matters: In session 1ea73ffd, Claude edited 3+ Python files without invoking the Python skill, leading to potential style/pattern inconsistencies.
┌─────────────────────────────────────────────────────────────┐
│ SESSION RESUMED - PYTHON SKILL VERIFICATION │
│ │
│ Before continuing: │
│ 1. Type hints on all functions? │
│ 2. Pydantic v2 for API validation? │
│ 3. ABOUTME headers on new files? │
│ 4. Run: ruff check <file>.py && mypy <file>.py │
│ 5. Re-invoke /python if skill context was lost │
└─────────────────────────────────────────────────────────────┘
# Direct iteration
for item in items:
process(item)
# With index
for i, item in enumerate(items):
print(f"{i}: {item}")
# Parallel iteration
for name, score in zip(names, scores, strict=True):
process(name, score)
# Reversed/sorted (no copy)
for item in reversed(items):
process(item)
# Safe access with default
port = config.get("port", 8080)
# Initialize-if-missing
groups: dict[str, list[str]] = {}
groups.setdefault(category, []).append(item)
# Merge dicts (Python 3.9+)
merged = defaults | overrides
# Dict comprehension
squares = {n: n**2 for n in range(10)}
# List comprehension
squared = [x**2 for x in numbers]
evens = [x for x in numbers if x % 2 == 0]
# Generator expression (memory efficient)
total = sum(x**2 for x in range(1_000_000))
any_match = any(item.is_valid for item in items)
# Set comprehension
unique_domains = {email.split("@")[1] for email in emails}
# Basic
x, y, z = coordinates
first, *rest = items
a, b = b, a # Swap
# Dict unpacking
combined = {**defaults, **overrides}
connect(**config) # Pass as kwargs
def greet(name: str, times: int = 1) -> str:
return f"Hello, {name}! " * times
def process(items: list[str]) -> dict[str, int]:
return {item: len(item) for item in items}
def find_user(user_id: int) -> User | None:
return db.get(user_id)
def process(value: int | str | None) -> str:
if value is None:
return "none"
return str(value)
from collections.abc import Sequence, Mapping, Iterable, Callable
def process_items(items: Sequence[str]) -> list[str]:
return [item.upper() for item in items]
def apply_func(data: Iterable[int], func: Callable[[int], int]) -> list[int]:
return [func(x) for x in data]
Use Pydantic for API validation and serialization. Full patterns: references/pydantic-patterns.md
from pydantic import BaseModel, Field, EmailStr
class User(BaseModel):
id: int
email: EmailStr
name: str = Field(min_length=1, max_length=100)
is_active: bool = True
# Parse and validate
user = User.model_validate({"id": 1, "email": "test@example.com", "name": "Alice"})
# Serialize
user.model_dump() # To dict
user.model_dump_json() # To JSON
| Data Type | Use | |-----------|-----| | API request/response | Pydantic BaseModel | | External JSON/dict shape | TypedDict (type hints only) | | Internal data container | dataclass | | Configuration | pydantic-settings |
try:
value = int(user_input)
except ValueError:
print("Invalid number")
except TypeError:
print("Wrong type")
# DON'T: bare except
try:
value = int(user_input)
except: # Catches KeyboardInterrupt too!
pass
# EAFP: Easier to Ask Forgiveness than Permission
try:
value = mapping[key]
except KeyError:
value = default
# vs LBYL: Look Before You Leap
if key in mapping:
value = mapping[key]
else:
value = default
class ValidationError(Exception):
def __init__(self, field: str, message: str):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
def validate_email(email: str) -> str:
if "@" not in email:
raise ValidationError("email", "Must contain @")
return email.lower()
# File handling (always use with)
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
# Multiple files
with open("in.txt") as infile, open("out.txt", "w") as outfile:
outfile.write(infile.read().upper())
# f-strings
name = "Alice"
score = 95.678
print(f"Score: {score:.2f}") # 95.68
print(f"{1000000:,}") # 1,000,000
print(f"{x=}") # Debug: x=42
# String building (use join, not +=)
result = " ".join(parts)
pathlib.Path for file pathslogging instead of printdef f(items=[]):from module import *except:eval() with untrusted input# Linting
ruff check .
ruff check --fix .
# Type checking
mypy src/
# Formatting
ruff format .
See references/advanced-patterns.md for:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer