用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/irahardianto/awesome-agv --skill python-idioms命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | python-idioms |
| description | Python type hints, Protocols, Pydantic, async/await, pytest, ruff, mypy strict. |
| paths | ["**/*.py","**/pyproject.toml","**/requirements*.txt"] |
Python rewards explicitness and readability over cleverness. Follow the Zen of Python. If it reads like plain English, it's probably idiomatic.
Scope: This skill covers Python-specific coding idioms. For file layout see
references/project-structure.md. For safety/SAST/performance patterns seereferences/python-patterns-and-anti-patterns.md. For logging seelogging-implementationskill. For quality commands seecode-idioms-and-conventionsrule.
pyproject.toml or *.py files, this skill does not applydjango in dependencies), co-load django-idioms skill alongside this one| Situation | Reference to Load |
|---|---|
| Starting a new project or setting up file layout | references/project-structure.md |
Choosing packages, pyproject.toml setup, or ruff config | references/recommended-dependencies.md |
| Writing code that handles user input, async operations, or I/O | references/python-patterns-and-anti-patterns.md |
from __future__ import annotations), template strings (PEP 750)type X = ... (PEP 695), @override decorator, improved f-strings, itertools.batchedStrEnum, ExceptionGroup + except*, asyncio.TaskGroup, tomllib, fine-grained error locationsmatch/case), X | Y union syntax, TypeAliasType hints are required for all public APIs, class attributes, and function signatures.
list, dict, set) for typing, not typing.List etc.X | Y instead of Union[X, Y] or Optional[X].type Vector[T] = list[T]@override (3.12+) to ensure methods actually override a base class method.TypeVar with constraints and bounds when necessary.Never for functions that always raise an exception or never return.# ❌ Anti-pattern: Untyped or legacy typing
from typing import List, Optional, TypeVar
T = TypeVar('T')
def process_items(items: List[T], strict: Optional[bool] = None) -> List[T]: ...
class Worker(BaseWorker):
def run(self): ... # Overrides base class? Maybe.
# ✅ Recommended pattern: Modern typing syntax
from typing import override, Never
type Vector[T] = list[T]
def process_items[T](items: Vector[T], strict: bool | None = None) -> Vector[T]: ...
class Worker(BaseWorker):
@override
def run(self) -> None: ...
def crash_and_burn(msg: str) -> Never:
raise RuntimeError(msg)
Protocols for Structural Subtyping
Define required behavior via Protocol instead of inheritance when depending on abstractions.
TypedDict for JSON/Dict payloads
When dealing with dictionaries that have a fixed schema, use TypedDict.
Exception.AppError base class).except Exception: pass). Use contextlib.suppress() if appropriate and intentional.except* (3.11+) when multiple errors can occur simultaneously.add_note() (3.11+) to attach additional context to exceptions before re-raising.None (DeepSource bug risk).finally blocks should not swallow exceptions; they are for cleanup only.# ❌ Anti-pattern: Broad except, swallowing errors, assigning None
def load_data():
try:
data = fetch()
return data
except Exception as e:
print(f"Failed: {e}")
finally:
return None # Swallows exception!
res = dict.get("key") # Might return None, then what?
# ✅ Recommended pattern: Specific exceptions, exception groups, add_note
class AppError(Exception): pass
class NetworkError(AppError): pass
def load_data() -> dict:
try:
return fetch()
except TimeoutError as e:
e.add_note("Timeout while fetching external data")
raise NetworkError("Failed to fetch") from e
# Exception groups (3.11+)
try:
raise ExceptionGroup("Multiple failures", [NetworkError(), ValueError()])
except* NetworkError as e:
handle_network(e)
except* ValueError as e:
handle_value(e)
@dataclass for internal data structures.@dataclass(frozen=True, slots=True) (3.10+) as the recommended default for value objects. slots=True avoids __dict__ creation, saving memory and speeding up attribute access.@dataclass(kw_only=True) (3.10+) to require keyword arguments.BaseModel when data crosses system boundaries (I/O, APIs, config) and requires validation.model_validator and field_validator for complex validation rules.# ✅ Recommended pattern: Dataclasses
from dataclasses import dataclass
@dataclass(frozen=True, slots=True, kw_only=True)
class UserConfig:
id: int
username: str
active: bool = True
# ✅ Recommended pattern: Pydantic Validation
from pydantic import BaseModel, field_validator, model_validator
class User(BaseModel):
password: str
password_confirm: str
@model_validator(mode="after")
def check_passwords_match(self) -> "User":
if self.password != self.password_confirm:
raise ValueError("Passwords do not match")
return self
When to use which:
| Use Case | Recommendation |
|---|---|
| Untrusted / external data (API input, config files, webhook payloads) | pydantic.BaseModel |
| Internal value objects, domain entities (no validation needed) | @dataclass(frozen=True, slots=True) |
| Dictionary-shaped typed data (JSON response shapes, kwargs mappings) | TypedDict |
| Named string or integer constants | enum.StrEnum / enum.IntEnum |
Prefer composition and dependency injection over deep inheritance hierarchies. Depend on typing.Protocol to define the interface a function or class expects.
# ✅ Recommended pattern: Dependency Injection with Protocols
from typing import Protocol
class MessageSender(Protocol):
def send(self, msg: str) -> None: ...
class EmailSender:
def send(self, msg: str) -> None:
pass # Implementation
def notify_user(sender: MessageSender) -> None:
sender.send("Hello")
asyncio.TaskGroup (3.11+) as the preferred way to run concurrent tasks over asyncio.gather. It provides structured concurrency and better error handling.asyncio.Runner (3.11+) for managing the event loop lifecycle instead of raw get_event_loop().asyncio.run() from inside an already running event loop.asyncio.to_thread() to offload blocking/CPU-bound work to a thread pool so the event loop is not blocked.# ❌ Anti-pattern: Unstructured concurrency
import asyncio
async def main():
await asyncio.gather(task1(), task2()) # Errors in one task don't cancel the other easily
# ✅ Recommended pattern: Structured concurrency with TaskGroup
import asyncio
async def main():
try:
async with asyncio.TaskGroup() as tg:
task1 = tg.create_task(fetch_data())
task2 = tg.create_task(process_data())
# tg automatically waits for all tasks. If one fails, others are cancelled.
except* Exception as e:
print(f"Task group failed: {e}")
| Entity | Convention | Example |
|---|---|---|
| Variables, Functions, Methods | snake_case | calculate_total() |
| Classes, Protocols, TypeAliases | PascalCase | UserRepository |
| Constants | UPPER_SNAKE_CASE | MAX_RETRIES |
| Protected/Private members | _leading_underscore | _internal_cache |
| Dunder methods | __dunder__ | __init__ |
fetch_user_by_id(user_id: int) is better than get_u(i).with statements for resource management (files, network connections, locks).yield for lazy evaluation and memory efficiency when dealing with large sequences.dataclasses.replace: Use for immutable updates to dataclasses.functools.cache / lru_cache: Use for memoizing expensive deterministic function calls.__slots__: Use via @dataclass(slots=True) or explicitly to save memory on heavily instantiated classes.StrEnum: (3.11+) Use for string-based enumerations.match/case for structural pattern matching instead of long if/elif/else chains.str.removeprefix() and str.removesuffix() instead of error-prone slicing or strip().dict1 | dict2 to merge dictionaries.:=: Use for assignment expressions to avoid repeating expensive calls or improving loop conditions.itertools.batched (3.12+): Use to cleanly chunk iterables into batches.pathlib.Path: ALWAYS prefer over os.path for file operations.[], {}). Use None as a sentinel. (DeepSource #1 bug risk)# ❌ Anti-pattern: Mutable default argument
def add_item(item: str, items: list = []) -> list:
items.append(item)
return items
# ✅ Recommended pattern: None sentinel
def add_item(item: str, items: list | None = None) -> list:
if items is None:
items = []
items.append(item)
return items
# ✅ Recommended pattern: Pattern matching & itertools.batched
import itertools
def process(command: dict | list):
match command:
case {"action": "delete", "id": int(id_val)}:
delete_record(id_val)
case list(items):
for batch in itertools.batched(items, 100):
process_batch(batch)
Write deterministic tests focusing on behavior.
pytest --cov=src --cov-report=term-missing@pytest.mark.parametrize for data-driven testing.pytest-asyncio for async tests.patch decorators when possible.Test Double Selection Table:
| Approach | When to Use |
|---|---|
| Hand-written fake (implement Protocol) | Simple interface, few methods, need stateful behavior |
pytest-mock (mocker fixture) | Verify call counts, argument matching |
respx | HTTP boundary mocking — intercepts httpx calls |
@pytest.mark.parametrize | Same logic, multiple input/output pairs |
Snapshot (syrupy) | Large outputs — JSON responses, CLI output |
hypothesis | Property-based testing for wide input spaces |
NEVER suppress these — they signal structural problems:
| Rule | What It Signals | What To Do Instead |
|---|---|---|
F841 (unused variable) | Dead code | Remove the variable |
S rules (security) | Security vulnerability | Fix the vulnerability |
B006 (mutable default) | Shared mutable state bug | Use None sentinel pattern |
ANN (missing annotations) | Untyped public API | Add type annotations |
E712 (== True/False/None) | Identity vs equality confusion | Use is / is not |
Acceptable suppressions (with mandatory # noqa: + reason comment):
| Rule | When Acceptable |
|---|---|
S101 (assert) | In test files only |
ANN101/ANN102 (self/cls annotations) | Standard convention — self/cls never need annotations |
T20 (print) | In CLI tools or scripts |
ARG (unused argument) | In interface implementations where signature is fixed |
Rule of thumb: If you're about to write # noqa:, stop and ask: "Am I suppressing a real design problem?"
Adopt the standard Rust/TS-style static analysis workflow:
| Phase | Command | Purpose |
|---|---|---|
| TDD / rapid iteration | mypy src/ --strict | Type-check only — fastest feedback |
| Pre-commit | ruff check . --fix | Lint — must pass with zero warnings |
| Pre-commit | ruff format . | Formatting — non-negotiable |
| Pre-commit | pytest | Unit tests — must all pass |
| Coverage verification | pytest --cov=src --cov-report=term-missing | Verify before merging |
| Security audit | bandit -r src/ -c pyproject.toml | Security scanning |
| Dependency audit | pip-audit | CVE scanning |
Configure all tools in pyproject.toml — never use per-file pragma comments to disable checks without a # noqa: reason comment.
Never use print() in production. Always use a configured logger (see logging-implementation skill).
Document all public items:
# ❌ Anti-pattern: Undocumented public API
def calculate_discount(price: float, rate: float) -> float:
return price * (1 - rate)
# ✅ Recommended pattern: Documented public API
def calculate_discount(price: float, rate: float) -> float:
"""Calculates the final price after applying a discount rate.
Args:
price: The original price.
rate: The discount rate as a decimal (e.g., 0.2 for 20%).
Returns:
The final discounted price.
Raises:
ValueError: If the rate is not between 0.0 and 1.0.
"""
if not (0.0 <= rate <= 1.0):
raise ValueError("Rate must be between 0.0 and 1.0")
return price * (1 - rate)
pip-audit in CI.pyproject.toml as the single source of truth for project metadata.uv.lock, requirements.lock).For the full curated dependency list with versions, see
references/recommended-dependencies.md.
os.environ / os.getenv() calls throughout the codebase.BaseSettings for validated, typed config.# ❌ Anti-pattern: Scattered os.getenv calls
import os
def connect_db():
db_url = os.getenv("DATABASE_URL") # Fails later if missing
# connect...
# ✅ Recommended pattern: Centralized typed config
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
api_key: str
# Fails immediately at startup if env vars are missing or invalid
settings = Settings()
def connect_db():
db_url = settings.database_url
# connect...
eval() or exec() with untrusted input.pickle on untrusted data.references/python-patterns-and-anti-patterns.md for the full catalog of safety and security patterns.perf-optimization skill for profiling and performance guidance.@code-idioms-and-conventions.md@references/project-structure.md@security-principles.md@architectural-pattern.md@testing-strategy.md@error-handling-principles.md@core-design-principles.md@logging-and-observability-mandate.md@.agents/skills/logging-implementation/SKILL.md@.agents/skills/django-idioms/SKILL.md@.agents/skills/testability-patterns/SKILL.md@concurrency-and-threading-principles.md@performance-optimization-principles.md@resources-and-memory-management-principles.md@security-mandate.md@dependency-management-principles.md@references/recommended-dependencies.md@references/python-patterns-and-anti-patterns.md