| name | python-fundamentals |
| description | Comprehensive Python best practices for Python 3.13+ based on PEP 8, Google Python Style Guide, and modern community standards. Use this skill for core Python patterns, type hints, data structures, error handling, and async programming. |
| auto_load | {"enabled":true,"triggers":{"keywords":["python",".py","dataclass","async","type hint","pyproject"],"file_patterns":["*.py"]},"priority":"high","load_with":[]} |
Python Fundamentals
Comprehensive Python best practices for Python 3.13+ based on PEP 8, Google Python Style Guide, and modern community standards. Use this skill for core Python patterns, type hints, data structures, error handling, and async programming.
When to Use This Skill
- Writing new Python code
- Applying type annotations
- Working with dataclasses, enums, or Pydantic
- Implementing error handling patterns
- Using async/await
- Handling file I/O with pathlib
- Following naming conventions and docstring standards
Type Annotations
Modern Syntax (Python 3.10+)
items: list[str]
mapping: dict[str, int]
optional: str | None
def fetch(url: str) -> dict | None:
...
def calculate(value: float) -> float:
...
Type Parameter Syntax (Python 3.12+)
def first[T](items: list[T]) -> T:
return items[0]
class Stack[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()
type Point = tuple[float, float]
type Vector[T] = list[T]
Abstract Types for Parameters
Use collections.abc for function parameters to accept any compatible type:
from collections.abc import Mapping, Sequence, Iterable
def transform(data: Mapping[str, int]) -> dict[str, str]:
return {k: str(v) for k, v in data.items()}
def process_all(items: Iterable[str]) -> list[str]:
return [item.upper() for item in items]
TypedDict for Structured Data
from typing import TypedDict, NotRequired
class UserData(TypedDict):
name: str
email: str
age: NotRequired[int]
def create_user(data: UserData) -> None:
...
Protocols (Structural Typing)
from typing import Protocol
class Readable(Protocol):
def read(self) -> str:
...
def process_readable(source: Readable) -> None:
content = source.read()
...
Data Structures
Choosing the Right Tool
| Use Case | Choice | Reason |
|---|
| Simple data container | dataclass | Standard library, no dependencies |
| Performance-critical | attrs with slots=True | Faster, more features |
| API boundaries | pydantic | Validation, JSON serialization |
| Immutable config | dataclass(frozen=True) | Prevents modification |
Dataclasses
from dataclasses import dataclass, field
@dataclass(slots=True)
class User:
name: str
email: str
tags: list[str] = field(default_factory=list)
@dataclass(frozen=True, slots=True)
class Config:
host: str
port: int = 8080
@dataclass
class Rectangle:
width: float
height: float
area: float = field(init=False)
def __post_init__(self):
self.area = self.width * self.height
Named Tuples
For simple immutable records:
from typing import NamedTuple
class Point(NamedTuple):
x: float
y: float
def distance_from_origin(self) -> float:
return (self.x ** 2 + self.y ** 2) ** 0.5
Enums
from enum import Enum, auto, StrEnum, IntEnum
class Status(Enum):
PENDING = "pending"
ACTIVE = "active"
COMPLETED = "completed"
class HttpMethod(StrEnum):
GET = auto()
POST = auto()
PUT = auto()
DELETE = auto()
class Priority(IntEnum):
LOW = 1
MEDIUM = 2
HIGH = 3
CRITICAL = 4
Error Handling
Principles
- Catch specific exceptions - Never bare
except: or broad except Exception:
- Minimize try scope - Only wrap code that may raise the expected exception
- Chain exceptions - Use
from to preserve context
- Fail fast - Validate early and raise meaningful errors
Patterns
try:
config = parse_config(path)
except FileNotFoundError:
config = default_config()
except json.JSONDecodeError as e:
raise ConfigError(f"Invalid JSON in {path}") from e
def process_file(path: Path) -> dict:
if not path.exists():
raise FileNotFoundError(f"File not found: {path}")
if not path.suffix == ".json":
raise ValueError(f"Expected JSON file, got: {path.suffix}")
...
Custom Exception Hierarchy
class AppError(Exception):
"""Base exception for application"""
pass
class NotFoundError(AppError):
"""Resource not found"""
def __init__(self, resource: str, id: int):
self.resource = resource
self.id = id
super().__init__(f"{resource} with id {id} not found")
class ValidationError(AppError):
"""Validation failed"""
def __init__(self, field: str, message: str):
self.field = field
self.message = message
super().__init__(f"{field}: {message}")
Exception Groups (Python 3.11+)
def validate_data(data: dict):
errors = []
if not data.get("name"):
errors.append(ValueError("name is required"))
if not data.get("email"):
errors.append(ValueError("email is required"))
if errors:
raise ExceptionGroup("Validation failed", errors)
try:
validate_data({})
except* ValueError as eg:
for error in eg.exceptions:
print(f"Validation error: {error}")
try:
process_data(data)
except ValueError as e:
e.add_note(f"Processing data: {data[:100]}...")
raise
Async Programming
Entry Point
import asyncio
async def main():
result = await fetch_data()
return result
if __name__ == "__main__":
asyncio.run(main())
Concurrent Execution
result1 = await fetch("url1")
result2 = await fetch("url2")
results = await asyncio.gather(
fetch("url1"),
fetch("url2"),
)
task1 = asyncio.create_task(fetch("url1"))
task2 = asyncio.create_task(fetch("url2"))
result1 = await task1
result2 = await task2
Rate Limiting with Semaphores
async def fetch_all(urls: list[str], max_concurrent: int = 10):
semaphore = asyncio.Semaphore(max_concurrent)
async def fetch_one(url: str):
async with semaphore:
return await fetch(url)
return await asyncio.gather(*[fetch_one(url) for url in urls])
Task Groups (Python 3.11+)
async def process_all(items: list[Item]):
async with asyncio.TaskGroup() as tg:
for item in items:
tg.create_task(process_item(item))
CPU-Bound Work
Offload CPU-intensive work to avoid blocking the event loop:
import asyncio
from concurrent.futures import ProcessPoolExecutor
async def process_images(paths: list[Path]):
loop = asyncio.get_running_loop()
with ProcessPoolExecutor() as pool:
results = await asyncio.gather(*[
loop.run_in_executor(pool, process_image, path)
for path in paths
])
return results
Timeout Handling
async def fetch_with_timeout(url: str, timeout: float = 5.0):
async with asyncio.timeout(timeout):
async with httpx.AsyncClient() as client:
return await client.get(url)
Resource Management
Context Managers
Always use context managers for resources that need cleanup:
with open(path, "r", encoding="utf-8") as f:
data = f.read()
with open(input_path) as src, open(output_path, "w") as dst:
dst.write(process(src.read()))
with connection.cursor() as cursor:
cursor.execute(query)
pathlib for File Operations
from pathlib import Path
content = Path("data.txt").read_text(encoding="utf-8")
Path("output.txt").write_text(result, encoding="utf-8")
data = Path("image.png").read_bytes()
Path("copy.png").write_bytes(data)
Custom Context Managers
from contextlib import contextmanager
@contextmanager
def temporary_directory():
import tempfile
import shutil
path = Path(tempfile.mkdtemp())
try:
yield path
finally:
shutil.rmtree(path)
from contextlib import asynccontextmanager
@asynccontextmanager
async def async_session() -> AsyncIterator[Session]:
session = await create_session()
try:
yield session
finally:
await session.close()
Path Handling
Use pathlib, Not Strings
from pathlib import Path
config_path = Path("data") / "config" / "settings.json"
project_root = Path.cwd()
home = Path.home()
Common Operations
path = Path("data/config/settings.json")
path.name
path.stem
path.suffix
path.parent
path.parts
path.exists()
path.is_file()
path.is_dir()
for file in path.parent.iterdir():
if file.suffix == ".json":
process(file)
for py_file in Path("src").rglob("*.py"):
analyze(py_file)
Security
user_path = Path(user_input)
safe_base = Path("/data/uploads")
if not user_path.resolve().is_relative_to(safe_base):
raise ValueError("Invalid path")
Pattern Matching (Python 3.10+)
def process_command(command: dict) -> str:
match command:
case {"action": "create", "name": str(name)}:
return f"Creating {name}"
case {"action": "delete", "id": int(id_)}:
return f"Deleting item {id_}"
case {"action": "update", "id": int(id_), "data": dict(data)}:
return f"Updating {id_} with {data}"
case {"action": action}:
return f"Unknown action: {action}"
case _:
return "Invalid command format"
def categorize_value(value):
match value:
case int(n) if n < 0:
return "negative"
case int(n) if n == 0:
return "zero"
case int(n) if n > 0:
return "positive"
case str(s) if len(s) > 10:
return "long-string"
case _:
return "other"
Functions and Classes
Function Design
def calculate_total(items: list[Item], tax_rate: float = 0.0) -> float:
"""Calculate total price including tax."""
subtotal = sum(item.price * item.quantity for item in items)
return subtotal * (1 + tax_rate)
def get_user(user_id: int) -> User | None:
if user_id <= 0:
return None
user = database.find(user_id)
if not user.is_active:
return None
return user
Avoid Mutable Default Arguments
def append_item(item, items=[]):
items.append(item)
return items
def append_item(item, items: list | None = None):
if items is None:
items = []
items.append(item)
return items
from dataclasses import dataclass, field
@dataclass
class Container:
items: list[str] = field(default_factory=list)
Class Design
class UserService:
def __init__(self, repository: UserRepository, cache: Cache):
self._repository = repository
self._cache = cache
class Rectangle:
def __init__(self, width: float, height: float):
self.width = width
self.height = height
@property
def area(self) -> float:
return self.width * self.height
Dependency Injection
from .db import database
def get_user(user_id: int) -> User:
return database.fetch(user_id)
def get_user(user_id: int, db: Database) -> User:
return db.fetch(user_id)
Naming Conventions
| Type | Style | Example |
|---|
| Module | lower_with_under | user_service.py |
| Package | lower_with_under | my_package/ |
| Class | CapWords | UserService |
| Exception | CapWords + Error | ValidationError |
| Function | lower_with_under | get_user_by_id() |
| Method | lower_with_under | calculate_total() |
| Variable | lower_with_under | user_count |
| Constant | CAPS_WITH_UNDER | MAX_RETRIES |
| Type Variable | CapWords | T, KeyType |
| Internal | _leading_under | _internal_helper |
Naming Guidelines
- Avoid abbreviations unfamiliar outside your project
- Single-character names only for iterators (
i, j) or math notation
- Boolean variables:
is_valid, has_permission, can_edit
- Collections: plural nouns (
users, items)
Imports
Organization
Three groups separated by blank lines, each sorted alphabetically:
import json
import sys
from pathlib import Path
from typing import TypedDict
import requests
from pydantic import BaseModel
from myapp.models import User
from myapp.utils import format_date
Rules
import os
result = os.path.exists(path)
from typing import TypedDict, Literal
from collections.abc import Mapping
from dataclasses import dataclass, field
Docstrings
Google Style Format
def fetch_users(
filters: dict[str, str],
limit: int = 100,
include_inactive: bool = False,
) -> list[User]:
"""Fetch users matching the given filters.
Retrieves users from the database that match all provided
filter criteria. Results are ordered by creation date.
Args:
filters: Key-value pairs for filtering (e.g., {"role": "admin"}).
limit: Maximum number of users to return.
include_inactive: Whether to include deactivated accounts.
Returns:
List of User objects matching the criteria, ordered by
creation date descending. Empty list if no matches.
Raises:
DatabaseError: If the database connection fails.
ValueError: If filters contains invalid keys.
Example:
>>> users = fetch_users({"department": "engineering"}, limit=10)
>>> len(users)
10
"""
Module and Class Docstrings
"""User management utilities.
This module provides functions for user CRUD operations
and authentication helpers.
"""
class UserService:
"""Service for user-related business logic.
Handles user creation, updates, and authentication.
All methods are transaction-safe.
Attributes:
repository: The underlying data access layer.
cache: Optional cache for read operations.
"""
Comprehensions and Generators
List Comprehensions
squares = [x ** 2 for x in range(10)]
names = [user.name for user in users if user.is_active]
results = []
for item in items:
if item.is_valid():
processed = transform(item)
if processed.meets_criteria():
results.append(processed)
Generator Expressions
Use for large datasets to save memory:
total = sum(order.amount for order in orders)
Dictionary Comprehensions
user_map = {user.id: user for user in users}
active_emails = {
user.id: user.email
for user in users
if user.is_active
}
Walrus Operator (Python 3.8+)
if (n := len(data)) > 10:
print(f"Processing {n} items")
filtered = [y for x in data if (y := transform(x)) is not None]
while (line := file.readline()):
process(line)
String Handling
F-Strings (Preferred)
name = "Alice"
count = 42
message = f"Hello, {name}! You have {count} messages."
message = f"Total: {price * quantity:.2f}"
print(f"{variable=}")
data = {"key": "value"}
print(f"Value: {data["key"]}")
Multi-line Strings
query = """
SELECT *
FROM users
WHERE active = true
"""
message = (
f"User {user.name} has been "
f"active for {user.days_active} days"
)
String Building
result = "".join(items)
result = ", ".join(str(x) for x in numbers)
Python 3.13+ Specific Features
Free-Threaded Mode (Experimental)
import threading
def cpu_bound_task(n):
"""CPU-intensive calculation that benefits from true parallelism"""
total = 0
for i in range(n):
total += i * i
return total
threads = []
for _ in range(4):
t = threading.Thread(target=cpu_bound_task, args=(10_000_000,))
threads.append(t)
t.start()
for t in threads:
t.join()
JIT Compiler (Experimental)
def fibonacci(n: int) -> int:
if n <= 1:
return n
a, b = 0, 1
for _ in range(n - 1):
a, b = b, a + b
return b
Improved Interactive REPL
Python 3.13 includes:
- Multiline editing with history preservation
- Colored prompts and tracebacks (default)
- F1: Interactive help browsing
- F2: History browsing (skips output)
- F3: Paste mode for larger code blocks
- Direct commands: help, exit, quit (no parentheses needed)
Memory Optimization
Using slots
class Point:
__slots__ = ('x', 'y')
def __init__(self, x: float, y: float):
self.x = x
self.y = y
Dataclasses with slots
@dataclass(slots=True)
class OptimizedData:
value: int
label: str
Anti-Patterns to Avoid
try:
result = risky_operation()
except:
pass
try:
result = operation()
except Exception:
result = default
try:
data = fetch_data()
processed = transform(data)
result = save(processed)
except ValueError:
...
def append_item(item, items=[]):
items.append(item)
return items
counter = 0
def increment():
global counter
counter += 1
References