Use this skill when writing Python code following PEP 8 and modern Python best practices...
This skill embeds PEP 8 (Python Enhancement Proposal 8) - the official style guide for Python code authored by Guido van Rossum, Barry Warsaw, and Alyssa Coghlan. It also incorporates modern Python best practices from the Google Python Style Guide and community standards.
import this
# Beautiful is better than ugly.
# Explicit is better than implicit.
# Simple is better than complex.
# Complex is better than complicated.
# Flat is better than nested.
# Sparse is better than dense.
# Readability counts.
# Special cases aren't special enough to break the rules.
# Although practicality beats purity.
# Errors should never pass silently.
# Unless explicitly silenced.
# In the face of ambiguity, refuse the temptation to guess.
# There should be one-- and preferably only one --obvious way to do it.
# Now is better than never.
# Although never is often better than *right* now.
# If the implementation is hard to explain, it's a bad idea.
# If the implementation is easy to explain, it may be a good idea.
# Namespaces are one honking great idea -- let's do more of those!
Python favors EAFP (Easier to Ask for Forgiveness than Permission) over LBYL (Look Before You Leap):
# EAFP (Pythonic)
try:
value = data["key"]["nested"]
except KeyError:
value = default_value
# LBYL (Avoid)
if "key" in data and "nested" in data["key"]:
value = data["key"]["nested"]
else:
value = default_value
| Element | Limit | Rationale |
|---|---|---|
| Line length | 79-88 chars | PEP 8: 79, Black: 88 |
| Function length | ≤25 lines | Single responsibility |
| Function parameters | ≤5 | Use dataclasses/kwargs for more |
| Nesting depth | ≤4 levels | Extract to functions |
| Import groups | 3 | stdlib → third-party → local |
# 4 spaces per indentation level (NEVER tabs)
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)
# Break BEFORE binary operators
income = (gross_wages
+ taxable_interest
+ (dividends - qualified_dividends)
- ira_deduction)
# Two blank lines around top-level definitions
class MyClass:
pass
def my_function():
pass
# Standard library imports first
import os
import sys
from pathlib import Path
# Third-party imports second (blank line separator)
import requests
from pydantic import BaseModel
# Local imports third (blank line separator)
from myapp.models import User
from myapp.utils import helpers
# NEVER use wildcard imports
# from module import * # BAD
| Type | Convention | Example |
|---|---|---|
| Modules | lowercase_underscore | my_module.py |
| Packages | lowercase | mypackage |
| Classes | PascalCase | MyClass |
| Functions | snake_case | my_function() |
| Variables | snake_case | my_variable |
| Constants | UPPER_SNAKE_CASE | MAX_SIZE |
| Private | _leading_underscore | _internal_var |
| "Private" class | __double_leading | __mangled |
# YES - spaces around operators
x = 1
y = x + 2
if x == 4:
print(x, y)
# YES - no spaces inside brackets
spam(ham[1], {eggs: 2})
foo = (0,)
# YES - no space before colon in slices
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3]
# YES - no spaces around = in keyword arguments
def complex(real, imag=0.0):
return magic(r=real, i=imag)
# YES - spaces around = with type annotations
def munge(sep: str = None): ...
from typing import Optional
from collections.abc import Sequence, Mapping
# Modern type hints (Python 3.10+)
def process_items(
items: list[str],
config: dict[str, int] | None = None,
) -> dict[str, list[str]]:
"""Process items with optional configuration."""
...
# Protocol for duck typing
from typing import Protocol
class Serializable(Protocol):
def to_dict(self) -> dict[str, Any]: ...
from dataclasses import dataclass, field
from datetime import datetime
@dataclass
class User:
username: str
email: str
created_at: datetime = field(default_factory=datetime.now)
is_active: bool = True
tags: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
self.email = self.email.lower()
from contextlib import contextmanager
# Always use context managers for resources
with open("file.txt") as f:
data = f.read()
# Custom context manager
@contextmanager
def database_transaction(connection):
transaction = connection.begin()
try:
yield transaction
transaction.commit()
except Exception:
transaction.rollback()
raise
import pytest
def test_user_creation_with_valid_email():
user = User(email="test@example.com", name="Test")
assert user.email == "test@example.com"
def test_user_creation_with_invalid_email_raises():
with pytest.raises(ValueError, match="Invalid email"):
User(email="invalid", name="Test")
@dataclass
class User:
email: str
name: str
def __post_init__(self) -> None:
if "@" not in self.email:
raise ValueError("Invalid email format")
import re
from dataclasses import dataclass
EMAIL_PATTERN = re.compile(r"^[\w\.-]+@[\w\.-]+\.\w+$")
@dataclass
class User:
email: str
name: str
def __post_init__(self) -> None:
self._validate_email()
self.email = self.email.lower()
def _validate_email(self) -> None:
if not EMAIL_PATTERN.match(self.email):
raise ValueError(f"Invalid email format: {self.email}")
# Define specific exceptions
class UserNotFoundError(Exception):
"""Raised when a user cannot be found."""
def __init__(self, user_id: int) -> None:
self.user_id = user_id
super().__init__(f"User {user_id} not found")
# Use exception chaining
def get_user(user_id: int) -> User:
try:
return database.fetch_user(user_id)
except DatabaseError as e:
raise UserNotFoundError(user_id) from e
# Catch specific exceptions
try:
result = process_data(data)
except ValueError as e:
logger.warning("Invalid data: %s", e)
result = default_value
except (IOError, OSError) as e:
logger.error("IO error: %s", e)
raise ProcessingError("Failed to process") from e
Before delivering Python code, verify:
ruff check)mypy passes without errorsOptional used for nullable typespytest tests pass# Modern Python toolchain
uv pip install ruff mypy pytest pytest-cov bandit
# Run quality checks
ruff check . # Linting (replaces flake8, isort)
ruff format . # Formatting (replaces black)
mypy . # Type checking
pytest --cov=src # Tests with coverage
bandit -r src/ # Security analysis
For detailed patterns and examples, see:
references/python-patterns.md - Comprehensive PEP 8 patternsreferences/testing-patterns.md - pytest and TDD patternsreferences/modern-python.md - Type hints, dataclasses, asyncQuick-start templates available in assets/templates/:
class_template.py - Standard class structureservice_class.py - Service object patternpytest_test.py - pytest test file templatecli_script.py - Command-line script template