| name | programming-python |
| description | Python programming skill based on PEP 8 and modern Python best practices - use for implementing Python code |
Python Programming Skill
Use this skill when writing or modifying Python code.
Follow [PEP 8 - Style Guide for Python Code](https://peps.python.org/pep-0008/) as the primary reference for style and conventions.
Python 3.8+ Standard. This project uses modern Python (3.8 or later). Use type hints, dataclasses, and modern features available in Python 3.8+.
Type hints are REQUIRED. All functions, methods, and class attributes must have type annotations. Use mypy for static type checking.
Readability counts. Python emphasizes clear, readable code. Follow the Zen of Python (PEP 20): "Explicit is better than implicit", "Simple is better than complex".
All code MUST be unit testable. Use dependency injection, avoid global state, and design for testability from the start.
Guidelines override existing code style. If you see existing code that violates the rules in this skill, apply these guidelines first and ignore the current project style. Do NOT propagate bad patterns just because they exist in the codebase.
The Zen of Python (PEP 20)
Key principles to guide Python development:
- 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
- Errors should never pass silently
- In the face of ambiguity, refuse the temptation to guess
Code Style (PEP 8)
Naming Conventions
import json_parser
from utils.data_processing import clean_data
class UserAccount:
pass
class HTTPConnectionPool:
pass
def calculate_total_price(items: list[Item]) -> float:
total_amount = sum(item.price for item in items)
return total_amount
MAX_CONNECTIONS = 100
DEFAULT_TIMEOUT = 30
class Database:
def _internal_method(self) -> None:
pass
def __very_private(self) -> None:
pass
_module_level_private = "hidden"
Indentation and Whitespace
def long_function_name(
var_one: str,
var_two: int,
var_three: dict[str, Any],
) -> bool:
"""Hanging indent for function arguments."""
print(var_one)
return True
result = some_function_that_takes_arguments(
argument1, argument2, argument3,
argument4, argument5
)
class MyClass:
pass
def my_function() -> None:
pass
class Example:
def method_one(self) -> None:
pass
def method_two(self) -> None:
pass
spam(ham[1], {eggs: 2})
if x == 4:
print(x, y)
x, y = y, x
spam( ham[ 1 ], { eggs: 2 } )
if x == 4 :
print(x , y)
x , y = y , x
Imports
import os
import sys
from typing import Any, Optional
import numpy as np
import requests
from myproject.utils import helper
from myproject.models import User
from module import *
from module import specific_function
import os
import sys
from typing import Any, Dict, List, Optional
Type Hints (REQUIRED)
Basic Type Hints
from typing import Any, Optional, Union
from collections.abc import Sequence, Mapping, Callable
name: str = "Alice"
age: int = 30
is_active: bool = True
scores: list[int] = [95, 87, 91]
user_data: dict[str, Any] = {"name": "Alice", "age": 30}
def greet(name: str) -> str:
return f"Hello, {name}!"
def process_data(
items: list[str],
batch_size: int = 10,
validate: bool = True,
) -> dict[str, int]:
"""Process items and return statistics."""
return {"processed": len(items), "batch_size": batch_size}
def find_user(user_id: int) -> Optional[User]:
"""Returns User if found, None otherwise."""
return users.get(user_id)
def parse_input(value: Union[str, int]) -> int:
"""Accept string or int, return int."""
return int(value)
def parse_input(value: str | int) -> int:
return int(value)
Advanced Type Hints
from typing import TypeVar, Generic, Protocol, Literal
from collections.abc import Callable, Iterator, Iterable
Callback = Callable[[str, int], bool]
def register_handler(callback: Callback) -> None:
pass
T = TypeVar('T')
def first(items: list[T]) -> Optional[T]:
return items[0] if items else None
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
class Drawable(Protocol):
def draw(self) -> None:
...
def render(obj: Drawable) -> None:
obj.draw()
def set_mode(mode: Literal["read", "write", "append"]) -> None:
pass
UserId = int
UserMap = dict[UserId, User]
users: UserMap = {1: User("Alice"), 2: User("Bob")}
Type Checking with mypy
result = legacy_function()
reveal_type(some_variable)
Modern Python Features (3.8+)
f-strings (Formatted String Literals)
name = "Alice"
age = 30
message = f"Hello, {name}! You are {age} years old."
formatted = f"Result: {value:.2f}"
debug = f"{variable=}"
message = "Hello, %s! You are %d years old." % (name, age)
message = "Hello, {}! You are {} years old.".format(name, age)
Dataclasses
from dataclasses import dataclass, field
@dataclass
class Point:
x: float
y: float
def distance(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
p = Point(3.0, 4.0)
print(p)
@dataclass
class User:
username: str
email: str
active: bool = True
roles: list[str] = field(default_factory=list)
_internal_id: int = field(default=0, repr=False, compare=False)
@dataclass(frozen=True)
class Config:
host: str
port: int
timeout: float = 30.0
Walrus Operator (:=) - Python 3.8+
if (match := pattern.search(text)) is not None:
print(match.group(0))
filtered = [y for x in data if (y := transform(x)) is not None]
while (line := file.readline()) != "":
process(line)
Pattern Matching (Python 3.10+)
def process_command(command: dict[str, Any]) -> str:
match command:
case {"action": "create", "item": item}:
return f"Creating {item}"
case {"action": "delete", "id": user_id}:
return f"Deleting user {user_id}"
case {"action": "update", "id": user_id, "data": data}:
return f"Updating {user_id} with {data}"
case _:
return "Unknown command"
Context Managers
with open("file.txt", "r") as f:
content = f.read()
from contextlib import contextmanager
from typing import Iterator
@contextmanager
def database_transaction(db: Database) -> Iterator[None]:
"""Context manager for database transactions."""
db.begin()
try:
yield
db.commit()
except Exception:
db.rollback()
raise
with database_transaction(db):
db.execute("INSERT INTO users ...")
db.execute("UPDATE accounts ...")
Generators and Iterators
from collections.abc import Iterator
def fibonacci(n: int) -> Iterator[int]:
"""Generate first n Fibonacci numbers."""
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
squares = (x**2 for x in range(1000000))
sum_of_squares = sum(x**2 for x in range(1000))
total = sum([x**2 for x in range(1000)])
total = sum(x**2 for x in range(1000))
Error Handling
Exceptions
try:
result = risky_operation()
except FileNotFoundError as e:
logger.error(f"File not found: {e}")
raise
except ValueError as e:
logger.warning(f"Invalid value: {e}")
return default_value
finally:
cleanup()
try:
risky_operation()
except:
pass
try:
process()
except (ValueError, TypeError) as e:
handle_error(e)
class ValidationError(ValueError):
"""Raised when validation fails."""
pass
class APIError(Exception):
"""Base exception for API errors."""
def __init__(self, message: str, status_code: int) -> None:
super().__init__(message)
self.status_code = status_code
def validate_age(age: int) -> None:
if age < 0:
raise ValidationError(f"Age cannot be negative: {age}")
if age > 150:
raise ValidationError(f"Age is unrealistic: {age}")
Exception Chaining
try:
result = parse_json(data)
except json.JSONDecodeError as e:
raise ValidationError("Invalid JSON data") from e
try:
result = alternative_parser(data)
except Exception:
raise ValidationError("Parsing failed") from None
EAFP vs LBYL
try:
value = dictionary[key]
except KeyError:
value = default
if key in dictionary:
value = dictionary[key]
else:
value = default
Docstrings
Google Style (Recommended)
def calculate_distance(
point1: tuple[float, float],
point2: tuple[float, float],
metric: str = "euclidean",
) -> float:
"""Calculate distance between two points.
Computes the distance between two 2D points using the specified
distance metric.
Args:
point1: First point as (x, y) coordinates.
point2: Second point as (x, y) coordinates.
metric: Distance metric to use. Options: "euclidean", "manhattan".
Defaults to "euclidean".
Returns:
The calculated distance as a float.
Raises:
ValueError: If metric is not supported.
Examples:
>>> calculate_distance((0, 0), (3, 4))
5.0