Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Python-specific design patterns and best practices including protocols, dataclasses, context managers, decorators, async/await, type hints, and package organization. Use when working with Python code to apply Pythonic patterns.
.claude/skills/affaan-m-python-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-15 | ✗→✓ | ▲ Improved | 37% | 0% |
| case-20 | ✓→✗ | ▼ Worse | 461% | 0% |
| case-14 | ✓→✓ | = Same ✓ | 902% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 707% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 253% | 0% |
用于构建健壮、高效和可维护应用程序的惯用 Python 模式与最佳实践。
Python 优先考虑可读性。代码应该清晰且易于理解。
python# Good: Clear and readable def 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 confusing def get_active_users(u): return [x for x in u if x.a]
避免魔法;清晰说明你的代码在做什么。
python# Good: Explicit configuration import logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) # Bad: Hidden side effects import some_module some_module.setup() # What does this do?
Python 倾向于使用异常处理而非检查条件。
python# Good: EAFP style def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any: try: return dictionary[key] except KeyError: return default_value # Bad: LBYL (Look Before You Leap) style def get_value(dictionary: dict, key: str, default_value: Any = None) -> Any: if key in dictionary: return dictionary[key] else: return default_value
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)
python# Python 3.9+ - Use built-in types def process_items(items: list[str]) -> dict[str, int]: return {item: len(item) for item in items} # Python 3.8 and earlier - Use typing module from typing import List, Dict def process_items(items: List[str]) -> Dict[str, int]: return {item: len(item) for item in items}
pythonfrom typing import TypeVar, Union # Type alias for complex types JSON = Union[dict[str, Any], list[Any], str, int, float, bool, None] def parse_json(data: str) -> JSON: return json.loads(data) # Generic types T = 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
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)
python# Good: Catch specific exceptions def 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 except def load_config(path: str) -> Config: try: with open(path) as f: return Config.from_json(f.read()) except: return None # Silent failure!
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
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 # Usage def 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
python# Good: Using context managers def process_file(path: str) -> str: with open(path, 'r') as f: return f.read() # Bad: Manual resource management def process_file(path: str) -> str: f = open(path, 'r') try: return f.read() finally: f.close()
pythonfrom contextlib import contextmanager @contextmanager def 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") # Usage with timer("data processing"): process_large_dataset()
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 # Usage with DatabaseTransaction(conn): user = conn.create_user(user_data) conn.create_profile(user.id, profile_data)
python# Good: List comprehension for simple transformations names = [user.name for user in users if user.is_active] # Bad: Manual loop names = [] for user in users: if user.is_active: names.append(user.name) # Complex comprehensions should be expanded # Bad: Too complex result = [x * 2 for x in items if x > 0 if x % 2 == 0] # Good: Use a generator function def 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
python# Good: Generator for lazy evaluation total = sum(x * x for x in range(1_000_000)) # Bad: Creates large intermediate list total = sum([x * x for x in range(1_000_000)])
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() # Usage for line in read_large_file("huge.txt"): process(line)
pythonfrom dataclasses import dataclass, field from datetime import datetime @dataclass class 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 # Usage user = User( id="123", name="Alice", email="alice@example.com" )
python@dataclass class 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}")
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 # Usage p1 = Point(0, 0) p2 = Point(3, 4) print(p1.distance(p2)) # 5.0
pythonimport functools import 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 @timer def slow_function(): time.sleep(1) # slow_function() prints: slow_function took 1.0012s
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!"]
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) @CountCalls def process(): pass # Each call to process() prints the call count
pythonimport concurrent.futures import 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
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
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))
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
└── .gitignorepython# Good: Import order - stdlib, third-party, local import os import sys from pathlib import Path import requests from fastapi import FastAPI from mypackage.models import User from mypackage.utils import format_name # Good: Use isort for automatic import sorting # pip install isort
python# mypackage/__init__.py """mypackage - A sample Python package.""" __version__ = "1.0.0" # Export main classes/functions at package level from mypackage.models import User, Post from mypackage.utils import format_name __all__ = ["User", "Post", "format_name"]
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 usage class Point: __slots__ = ['x', 'y'] def __init__(self, x: float, y: float): self.x = x self.y = y
python# Bad: Returns full list in memory def read_lines(path: str) -> list[str]: with open(path) as f: return [line.strip() for line in f] # Good: Yields lines one at a time def read_lines(path: str) -> Iterator[str]: with open(path) as f: for line in f: yield line.strip()
python# Bad: O(n²) due to string immutability result = "" for item in items: result += str(item) # Good: O(n) using join result = "".join(str(item) for item in items) # Good: Using StringIO for building from io import StringIO buffer = StringIO() for item in items: buffer.write(str(item)) result = buffer.getvalue()
bash# Code formatting black . isort . # Linting ruff check . pylint mypackage/ # Type checking mypy . # Testing pytest --cov=mypackage --cov-report=html # Security scanning bandit -r . # Dependency management pip-audit safety check
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 = 88 target-version = ['py39'] [tool.ruff] line-length = 88 select = ["E", "F", "I", "N", "W"] [tool.mypy] python_version = "3.9" warn_return_any = true warn_unused_configs = true disallow_untyped_defs = true [tool.pytest.ini_options] testpaths = ["tests"] addopts = "--cov=mypackage --cov-report=term-missing"
| 惯用法 | 描述 | |-------|-------------| | EAFP | 请求宽恕比请求许可更容易 | | 上下文管理器 | 使用 with 进行资源管理 | | 列表推导式 | 用于简单的转换 | | 生成器 | 用于惰性求值和大数据集 | | 类型提示 | 注解函数签名 | | 数据类 | 用于具有自动生成方法的数据容器 | | __slots__ | 用于内存优化 | | f-strings | 用于字符串格式化(Python 3.6+) | | pathlib.Path | 用于路径操作(Python 3.4+) | | enumerate | 用于循环中的索引-元素对 |
python# Bad: Mutable default arguments def append_to(item, items=[]): items.append(item) return items # Good: Use None and create new list def 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 isinstance if isinstance(obj, list): process(obj) # Bad: Comparing to None with == if value == None: process() # Good: Use is if value is None: process() # Bad: from module import * from os.path import * # Good: Explicit imports from os.path import join, exists # Bad: Bare except try: risky_operation() except: pass # Good: Specific exception try: risky_operation() except SpecificError as e: logger.error(f"Operation failed: {e}")
记住:Python 代码应该具有可读性、显式性,并遵循最小意外原则。如有疑问,优先考虑清晰性而非巧妙性。
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-14 | pass→pass | 3,276 | 3,460 | +6% | 1 | 1 | 0% | 544 | 5,451 | +902% | 0 | 0 | — |
case-01 | pass→pass | 3,729 | 5,750 | +54% | 1 | 1 | 0% | 743 | 5,998 | +707% | 0 | 0 | — |
case-02 | pass→pass | 7,371 | 6,113 | -17% | 1 | 1 | 0% | 1,748 | 6,177 | +253% | 0 | 0 | — |
case-03 | pass→pass | 4,715 | 4,926 | +4% | 1 | 1 | 0% | 1,044 | 5,904 | +466% | 0 | 0 | — |
case-04 | pass→pass | 7,487 | 7,325 | -2% | 1 | 1 | 0% | 1,503 | 6,471 | +331% | 0 | 0 | — |
case-05 | pass→pass | 3,760 | 3,690 | -2% | 1 | 1 | 0% | 818 | 5,629 | +588% | 0 | 0 | — |
case-06 | pass→pass | 5,467 | 6,253 | +14% | 1 | 1 | 0% | 1,089 | 6,124 | +462% | 0 | 0 | — |
case-07 | pass→pass | 4,071 | 4,807 | +18% | 1 | 1 | 0% | 878 | 5,647 | +543% | 0 | 0 | — |
case-08 | pass→pass | 5,778 | 6,638 | +15% | 1 | 1 | 0% | 1,230 | 6,221 | +406% | 0 | 0 | — |
case-09 | pass→pass | 9,528 | 8,614 | -10% | 1 | 1 | 0% | 2,080 | 6,639 | +219% | 0 | 0 | — |
case-10 | pass→pass | 5,805 | 6,099 | +5% | 1 | 1 | 0% | 1,251 | 6,113 | +389% | 0 | 0 | — |
case-11 | pass→pass | 7,022 | 5,320 | -24% | 1 | 1 | 0% | 1,406 | 5,900 | +320% | 0 | 0 | — |
case-12 | pass→pass | 2,262 | 2,280 | +1% | 1 | 1 | 0% | 417 | 5,199 | +1147% | 0 | 0 | — |
case-13 | pass→pass | 4,343 | 5,657 | +30% | 1 | 1 | 0% | 821 | 5,840 | +611% | 0 | 0 | — |
case-15 | fail→pass | 27,398 | 15,746 | -43% | 1 | 1 | 0% | 5,838 | 7,992 | +37% | 0 | 0 | — |
case-16 | pass→pass | 12,763 | 11,326 | -11% | 1 | 1 | 0% | 2,553 | 7,166 | +181% | 0 | 0 | — |
case-17 | pass→pass | 3,580 | 4,459 | +25% | 1 | 1 | 0% | 698 | 5,479 | +685% | 0 | 0 | — |
case-18 | pass→pass | 2,755 | 3,520 | +28% | 1 | 1 | 0% | 551 | 5,511 | +900% | 0 | 0 | — |
case-19 | pass→pass | 5,949 | 4,852 | -18% | 1 | 1 | 0% | 1,151 | 5,762 | +401% | 0 | 0 | — |
case-20 | pass→fail | 5,219 | 6,919 | +33% | 1 | 1 | 0% | 1,079 | 6,058 | +461% | 0 | 0 | — |
case-21 | pass→pass | 2,248 | 3,930 | +75% | 1 | 1 | 0% | 395 | 5,601 | +1318% | 0 | 0 | — |
case-22 | pass→pass | 5,263 | 8,068 | +53% | 1 | 1 | 0% | 1,083 | 6,353 | +487% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of 0 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.