Agent Skills: Python Style Guide

Comprehensive Python programming guidelines based on Google's Python Style Guide. Use when you needs to write Python code, review Python code for style issues, refactor Python code, or provide Python programming guidance. Covers language rules (imports, exceptions, type annotations), style rules (naming conventions, formatting, docstrings), and best practices for clean, maintainable Python code.

UncategorizedID: CodingKaiser/claude-kaiser-skills/python-style-guide

Install this agent skill to your local

pnpm dlx add-skill https://github.com/CodingKaiser/kaiser-skills/tree/HEAD/python-style-guide

Skill Files

Browse the full folder contents for python-style-guide.

Download Skill

Loading file tree…

python-style-guide/SKILL.md

Skill Metadata

Name
python-style-guide
Description
Comprehensive Python programming guidelines based on Google's Python Style Guide. Use when you needs to write Python code, review Python code for style issues, refactor Python code, or provide Python programming guidance. Covers language rules (imports, exceptions, type annotations), style rules (naming conventions, formatting, docstrings), and best practices for clean, maintainable Python code.

Python Style Guide

Comprehensive guidelines for writing clean, maintainable Python code based on Google's Python Style Guide.

Core Philosophy

BE CONSISTENT. Match the style of the code around you. Use these guidelines as defaults, but always prioritize consistency with existing code.

Language Rules

Imports

Use import statements for packages and modules in application code to avoid circular dependencies. For standard library and third-party packages, importing classes is acceptable.

Yes:

from pydantic import BaseModel  # Third-party: Class import OK
from pathlib import Path        # Stdlib: Class import OK
import sound_effects.utils      # App: Module import
from myproject import config    # App: Module import

No:

from myproject.utils import heavy_function  # App: Avoid direct function import if circular dep risk

Import Formatting

  • Group imports: standard library, third-party, application-specific
  • Alphabetize within each group
  • Use absolute imports (not relative imports)
  • One import per line (except for multiple items from typing or collections.abc)
# Standard library
import os
import sys

# Third-party
import numpy as np
import tensorflow as tf

# Application-specific
from myproject.backend import api_utils

Exceptions

Use exceptions appropriately. Do not suppress errors with bare except: clauses.

Yes:

try:
    result = risky_operation()
except ValueError as e:
    logging.error(f"Invalid value: {e}")
    raise

No:

try:
    result = risky_operation()
except:  # Too broad, hides bugs
    pass

Type Annotations

Annotate all function signatures. Type annotations improve code readability and catch errors early.

General rules:

  • Annotate all public APIs
  • Use built-in types (list, dict, set) instead of typing.List, etc. (Python 3.9+)
  • Import typing symbols directly: from typing import Any, Union
  • Use None instead of type(None) or NoneType
def fetch_data(url: str, timeout: int = 30) -> dict[str, Any]:
    """Fetch data from URL."""
    ...

def process_items(items: list[str]) -> None:
    """Process a list of items."""
    ...

Default Argument Values

Never use mutable objects as default values in function definitions.

Yes:

def foo(a: int, b: list[int] | None = None) -> None:
    if b is None:
        b = []

No:

def foo(a: int, b: list[int] = []) -> None:  # Mutable default - WRONG!
    b.append(a)

True/False Evaluations

Use implicit false where possible. Empty sequences, None, and 0 are false in boolean contexts.

Yes:

if not users:  # Preferred
if not some_dict:
if value:

No:

if len(users) == 0:  # Verbose
if users == []:
if value == True:  # Never compare to True/False explicitly

Comprehensions & Generators

Use comprehensions and generators for simple cases. Keep them readable.

Yes:

result = [x for x in data if x > 0]
squares = (x**2 for x in range(10))

No:

# Too complex
result = [
    x.strip().lower() for x in data 
    if x and len(x) > 5 and not x.startswith('#')
    for y in x.split(',') if y
]  # Use a regular loop instead

Lambda Functions

Use lambdas for one-liners only. For anything complex, define a proper function.

Yes:

sorted(data, key=lambda x: x.timestamp)

Acceptable but prefer named function:

def get_timestamp(item):
    return item.timestamp

sorted(data, key=get_timestamp)

Style Rules

Line Length

Maximum line length: 88 characters. Exceptions allowed for imports, URLs, and long strings that can't be broken.

Indentation

Use 4 spaces per indentation level. Never use tabs.

For hanging indents, align wrapped elements vertically or use 4-space hanging indent:

# Aligned with opening delimiter
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Hanging indent (4 spaces)
foo = long_function_name(
    var_one, var_two, var_three,
    var_four)

Blank Lines

  • Two blank lines between top-level definitions
  • One blank line between method definitions
  • Use blank lines sparingly within functions to show logical sections

Naming Conventions

| Type | Convention | Examples | |------|-----------|----------| | Packages/Modules | lower_with_under | my_module.py | | Classes | CapWords | MyClass | | Functions/Methods | lower_with_under() | my_function() | | Constants | CAPS_WITH_UNDER | MAX_SIZE | | Variables | lower_with_under | my_var | | Private | _leading_underscore | _private_var |

Avoid:

  • Single character names except for counters/iterators (i, j, k)
  • Dashes in any name
  • __double_leading_and_trailing_underscore__ (reserved for Python)

Comments and Docstrings

Docstring Format

Use Google-style docstrings for all public modules, functions, classes, and methods.

Function docstring:

def fetch_smalltable_rows(
    table_handle: smalltable.Table,
    keys: Sequence[bytes | str],
    require_all_keys: bool = False,
) -> Mapping[bytes, tuple[str, ...]]:
    """Fetches rows from a Smalltable.

    Retrieves rows pertaining to the given keys from the Table instance
    represented by table_handle. String keys will be UTF-8 encoded.

    Args:
        table_handle: An open smalltable.Table instance.
        keys: A sequence of strings representing the key of each table
            row to fetch. String keys will be UTF-8 encoded.
        require_all_keys: If True, raise ValueError if any key is missing.

    Returns:
        A dict mapping keys to the corresponding table row data
        fetched. Each row is represented as a tuple of strings.

    Raises:
        IOError: An error occurred accessing the smalltable.
        ValueError: A key is missing and require_all_keys is True.
    """
    ...

Class docstring:

class SampleClass:
    """Summary of class here.

    Longer class information...
    Longer class information...

    Attributes:
        likes_spam: A boolean indicating if we like SPAM or not.
        eggs: An integer count of the eggs we have laid.
    """

    def __init__(self, likes_spam: bool = False):
        """Initializes the instance based on spam preference.

        Args:
            likes_spam: Defines if instance exhibits this preference.
        """
        self.likes_spam = likes_spam
        self.eggs = 0

Block and Inline Comments

  • Use complete sentences with proper capitalization
  • Block comments indent to the same level as the code
  • Inline comments should be separated by at least 2 spaces
  • Use inline comments sparingly
# Block comment explaining the following code.
# Can span multiple lines.
x = x + 1  # Inline comment (use sparingly)

Strings

Use f-strings for formatting (Python 3.6+).

Yes:

x = f"name: {name}; score: {score}"

Acceptable:

x = "name: %s; score: %d" % (name, score)
x = "name: {}; score: {}".format(name, score)

No:

x = "name: " + name + "; score: " + str(score)  # Avoid + for formatting

Logging

Use Loguru for logging with brace-style lazy formatting:

logger.info("Request from {} resulted in {}", ip_address, status_code)

Avoid standard logging with % formatting.

Files and Resources

For simple text operations, prefer pathlib methods:

data = Path("file.txt").read_text()
Path("output.txt").write_text("content")

For complex operations or non-text files, use context managers:

with open("image.png", "rb") as f:
    data = f.read()

Statements

Generally avoid multiple statements on one line.

Yes:

if foo:
    bar()

No:

if foo: bar()  # Avoid

Main

For executable scripts, use:

def main():
    ...

if __name__ == "__main__":
    main()

Function Length

Keep functions focused and reasonably sized. If a function exceeds about 40 lines, consider splitting it unless it remains very readable.

Type Annotation Details

Forward Declarations

Use string quotes for forward references:

class MyClass:
    def method(self) -> "MyClass":
        return self

Type Aliases

Create aliases for complex types:

from typing import TypeAlias

ConnectionOptions: TypeAlias = dict[str, str]
Address: TypeAlias = tuple[str, int]
Server: TypeAlias = tuple[Address, ConnectionOptions]

TypeVars

Use descriptive names for TypeVars:

from typing import TypeVar

_T = TypeVar("_T")  # Good: private, unconstrained
AddableType = TypeVar("AddableType", int, float, str)  # Good: descriptive

Generics

Always specify type parameters for generic types:

Yes:

def get_names(employee_ids: list[int]) -> dict[int, str]:
    ...

No:

def get_names(employee_ids: list) -> dict:  # Missing type parameters
    ...

Imports for Typing

Import typing symbols directly:

from collections.abc import Mapping, Sequence
from typing import Any, Union

# Use built-in types for containers (Python 3.9+)
def foo(items: list[str]) -> dict[str, int]:
    ...

Modern Python Features

Match Statements (Python 3.10+)

Use structural pattern matching for complex conditionals:

def handle_response(response: dict) -> str:
    match response:
        case {"status": "ok", "data": data}:
            return f"Success: {data}"
        case {"status": "error", "message": msg}:
            return f"Error: {msg}"
        case {"status": status}:
            return f"Unknown status: {status}"
        case _:
            return "Invalid response"

Pattern matching with types:

def process(value: int | str | list) -> str:
    match value:
        case int(n) if n > 0:
            return f"Positive int: {n}"
        case int(n):
            return f"Non-positive int: {n}"
        case str(s):
            return f"String: {s}"
        case [first, *rest]:
            return f"List starting with {first}"

Dataclasses with Slots (Python 3.10+)

Use slots=True for memory efficiency and faster attribute access:

from dataclasses import dataclass

@dataclass(slots=True)
class Point:
    x: float
    y: float

@dataclass(slots=True, frozen=True)
class ImmutableConfig:
    host: str
    port: int
    timeout: float = 30.0

Postponed Annotation Evaluation

Use from __future__ import annotations for:

  • Forward references without quotes
  • Faster module import (annotations not evaluated at definition time)
from __future__ import annotations

class Node:
    def __init__(self, children: list[Node]) -> None:  # No quotes needed
        self.children = children

    def add_child(self, child: Node) -> None:
        self.children.append(child)

Exception Groups (Python 3.11+)

Handle multiple exceptions simultaneously:

try:
    async with asyncio.TaskGroup() as tg:
        tg.create_task(task1())
        tg.create_task(task2())
except* ValueError as eg:
    for exc in eg.exceptions:
        logger.error("ValueError: {}", exc)
except* TypeError as eg:
    for exc in eg.exceptions:
        logger.error("TypeError: {}", exc)

Common Patterns

Properties

Use properties for simple attribute access:

class Square:
    def __init__(self, side: float):
        self._side = side
    
    @property
    def area(self) -> float:
        return self._side ** 2

Conditional Expressions

Use ternary operators for simple conditions:

x = "yes" if condition else "no"

Context Managers

Create custom context managers when appropriate:

from contextlib import contextmanager

@contextmanager
def managed_resource(*args, **kwargs):
    resource = acquire_resource(*args, **kwargs)
    try:
        yield resource
    finally:
        release_resource(resource)

Linting

Run ruff on all Python code. Suppress warnings only when necessary:

dict = 'something'  # noqa: A001

Package __init__.py Files

THERE MUST BE NO CODE IN __init__.py FILES. Keep them empty.

# __init__.py
# This file should be empty

Import from modules directly:

# Instead of: from mypackage import MyClass
# Use: from mypackage.core import MyClass

Preferred Libraries

Use these libraries when applicable:

| Purpose | Library | |---------|---------| | Data validation/models | pydantic | | Logging | loguru | | CLI | cyclopts, rich | | Testing | pytest, pytest-mock |

Summary

When writing Python code:

  1. Use type annotations for all functions
  2. Follow naming conventions consistently
  3. Write clear docstrings for all public APIs
  4. Keep functions focused and reasonably sized
  5. Use comprehensions for simple cases
  6. Prefer implicit false in boolean contexts
  7. Use f-strings for formatting
  8. Always use context managers for resources
  9. Run ruff check and ruff format
  10. Keep __init__.py files empty
  11. BE CONSISTENT with existing code

Additional Resources

For detailed reference on specific topics, see:

  • references/advanced_types.md - Advanced type annotation patterns including Protocol, TypedDict, Literal, ParamSpec, and more
  • references/antipatterns.md - Common Python mistakes and their fixes
  • references/docstring_examples.md - Comprehensive docstring examples for all Python constructs