mcpField
P
python-best-practices

python-best-practices

github.com/jefflester開発4337 回インストール6 日前に更新
リポジトリ

python-best-practices について

Python 開発で品質と一貫性を保つための指針を示すスキルです。PEP 8、型ヒント、docstring、例外処理、コード構成のパターンを扱います。

主な機能

  1. 01PEP 8 のスタイル指針
  2. 02型ヒントと現代的な構文
  3. 03NumPy スタイルの docstring
  4. 04例外処理とコンテキストマネージャー
  5. 05データクラス、列挙型、pathlib
  6. 06pytest によるテスト

活用シーン

  1. 01一貫したスタイルの Python コード作成
  2. 02関数やクラスへの型ヒントと文書の追加
  3. 03ファイルやモジュール構成のリファクタリング
  4. 04例外処理やファイル操作の改善

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jefflester/claude-skills-supercharged --skill python-best-practices

インストール後、Claude Code で/python-best-practicesを入力して読み込まれたか確認します。

詳細

Python のベストプラクティス

目的

このスキルでは、Python プロジェクト全体でコードの品質、保守性、一貫性を確保するための、Python 開発のベストプラクティスを説明します。

このスキルを使用するタイミング

次の場合に自動的に有効になります:

  • Python ファイル(*.py)を扱うとき
  • 「python」、「best practices」、「style guide」に言及したとき
  • 型ヒントや docstring を追加するとき
  • Python コードをリファクタリングするとき

スタイルガイドライン

PEP 8 への準拠

Python コードでは PEP 8 スタイルガイドに従います:

  • インデント:インデントレベルごとに 4 つのスペース
  • 行の長さ:コードは最大 79 文字、docstring とコメントは 72 文字
  • 空行:トップレベルの定義間には 2 行、メソッド間には 1 行
  • インポート:必ずファイルの先頭に配置し、標準ライブラリ、サードパーティ、ローカルの順にグループ化
  • 命名規則:
    • 関数、変数、モジュールには snake_case
    • クラスには PascalCase
    • 定数には UPPER_SNAKE_CASE
    • 内部用・非公開には先頭にアンダースコアを付けた _name
インポートの整理

インポートは必ず次の順序で整理します:

# 1. Standard library imports
import os
import sys
from pathlib import Path

# 2. Third-party imports
import requests
import numpy as np

# 3. Local application imports
from myapp.core import MyClass
from myapp.utils import helper_function

TYPE_CHECKING を使用して循環インポートを回避します:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from myapp.other_module import OtherClass

def my_function(obj: "OtherClass") -> None:
    """Function that uses OtherClass only for type hints."""
    pass

型ヒント

型ヒントを必ず使用する

型ヒントを使うとコードが明確になり、エラーを早期に検出できます:

def process_data(
    items: list[str],
    max_count: int | None = None,
    verbose: bool = False
) -> dict[str, int]:
    """Process items and return counts.

    Parameters
    ----------
    items : list[str]
        List of items to process
    max_count : int | None, optional
        Maximum items to process, by default None
    verbose : bool, optional
        Enable verbose output, by default False

    Returns
    -------
    dict[str, int]
        Dictionary mapping items to counts
    """
    result: dict[str, int] = {}

    for item in items[:max_count]:
        result[item] = result.get(item, 0) + 1
        if verbose:
            print(f"Processed: {item}")

    return result
最新の型構文(Python 3.10+)

Union の代わりに | を使った最新の共用体構文を使用します:

# Good (Python 3.10+)
def get_value(key: str) -> int | None:
    pass

# Avoid (old style)
from typing import Union, Optional
def get_value(key: str) -> Optional[int]:
    pass
ジェネリック型

組み込みのジェネリック型を使用します(Python 3.9+):

# Good (Python 3.9+)
def process_list(items: list[str]) -> dict[str, int]:
    pass

# Avoid (old style)
from typing import List, Dict
def process_list(items: List[str]) -> Dict[str, int]:
    pass

docstring

NumPy スタイルの docstring

一貫性を保つため、NumPy スタイルの docstring を使用します:

def calculate_statistics(
    data: list[float],
    include_median: bool = True
) -> dict[str, float]:
    """Calculate statistical measures for a dataset.

    This function computes mean, standard deviation, and optionally
    median for the provided dataset.

    Parameters
    ----------
    data : list[float]
        List of numerical values to analyze
    include_median : bool, optional
        Whether to calculate median, by default True

    Returns
    -------
    dict[str, float]
        Dictionary containing:
        - 'mean': arithmetic mean
        - 'std': standard deviation
        - 'median': median value (if include_median=True)

    Raises
    ------
    ValueError
        If data is empty or contains non-numeric values

    Examples
    --------
    >>> calculate_statistics([1.0, 2.0, 3.0, 4.0, 5.0])
    {'mean': 3.0, 'std': 1.414, 'median': 3.0}

    Notes
    -----
    Standard deviation uses Bessel's correction (ddof=1).
    """
    if not data:
        raise ValueError("Data cannot be empty")

    # Implementation here
    pass
クラスの docstring
class DataProcessor:
    """Process and transform data from various sources.

    This class provides methods for loading, transforming, and
    validating data from multiple input formats.

    Parameters
    ----------
    source_dir : Path
        Directory containing source data files
    cache_enabled : bool, optional
        Enable result caching, by default True

    Attributes
    ----------
    source_dir : Path
        Directory path for source files
    cache : dict[str, Any]
        Cache for processed results

    Examples
    --------
    >>> processor = DataProcessor(Path("/data"))
    >>> results = processor.process_files()
    """

    def __init__(self, source_dir: Path, cache_enabled: bool = True):
        """Initialize the DataProcessor."""
        self.source_dir = source_dir
        self.cache: dict[str, Any] = {} if cache_enabled else None

エラー処理

具体的な例外型

裸の except ではなく、具体的な例外型を使用します:

# Good
try:
    with open(file_path) as f:
        data = f.read()
except FileNotFoundError:
    logger.error(f"File not found: {file_path}")
    raise
except PermissionError:
    logger.error(f"Permission denied: {file_path}")
    raise

# Avoid
try:
    with open(file_path) as f:
        data = f.read()
except:  # Too broad!
    pass
コンテキストマネージャー

リソースには必ずコンテキストマネージャーを使用します:

# Good
with open(file_path) as f:
    content = f.read()

# Avoid
f = open(file_path)
content = f.read()
f.close()  # Easy to forget!
カスタム例外

ドメイン固有のエラーにはカスタム例外を定義します:

class ValidationError(Exception):
    """Raised when data validation fails."""
    pass

class DataProcessingError(Exception):
    """Raised when data processing encounters an error."""

    def __init__(self, message: str, item_id: str):
        super().__init__(message)
        self.item_id = item_id

よく使われるパターン

データ構造には dataclass を使用する

シンプルなデータコンテナーには dataclasses を使用します:

from dataclasses import dataclass, field

@dataclass
class User:
    """User profile information."""

    username: str
    email: str
    age: int
    tags: list[str] = field(default_factory=list)
    is_active: bool = True

    def __post_init__(self):
        """Validate fields after initialization."""
        if self.age < 0:
            raise ValueError("Age cannot be negative")
固定された値の集合には Enum を使用する

固定された値の集合には Enum を使用します:

from enum import Enum, auto

class Status(Enum):
    """Processing status values."""

    PENDING = auto()
    PROCESSING = auto()
    COMPLETED = auto()
    FAILED = auto()

# Usage
current_status = Status.PENDING
if current_status == Status.COMPLETED:
    print("Done!")
ファイル操作には pathlib を使用する

os.path の代わりに pathlib.Path を使用します:

from pathlib import Path

# Good
data_dir = Path("/data")
file_path = data_dir / "input.txt"

if file_path.exists():
    content = file_path.read_text()

# Avoid
import os
data_dir = "/data"
file_path = os.path.join(data_dir, "input.txt")

if os.path.exists(file_path):
    with open(file_path) as f:
        content = f.read()
リスト内包表記

明確さとパフォーマンスのために内包表記を使用します:

# Good
squared = [x**2 for x in range(10) if x % 2 == 0]

# Avoid
squared = []
for x in range(10):
    if x % 2 == 0:
        squared.append(x**2)

コードの整理

モジュールの構成

モジュールを明確なセクションに分けて整理します:

"""Module for data processing utilities.

This module provides functions for loading, transforming, and
validating data from various sources.
"""

# Standard library imports
import os
import sys
from pathlib import Path

# Third-party imports
import requests
import pandas as pd

# Local imports
from myapp.core import BaseProcessor
from myapp.utils import validate_input

# Constants
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

# Exceptions
class ProcessingError(Exception):
    """Raised when processing fails."""
    pass

# Functions
def load_data(source: str) -> pd.DataFrame:
    """Load data from source."""
    pass

# Classes
class DataProcessor(BaseProcessor):
    """Process and validate data."""
    pass

# Module initialization
if __name__ == "__main__":
    # CLI entry point
    main()
マジックナンバーを避ける

マジックナンバーの代わりに名前付き定数を使用します:

# Good
MAX_RETRIES = 3
TIMEOUT_SECONDS = 30

def fetch_data(url: str) -> dict:
    for attempt in range(MAX_RETRIES):
        response = requests.get(url, timeout=TIMEOUT_SECONDS)
        if response.status_code == 200:
            return response.json()

# Avoid
def fetch_data(url: str) -> dict:
    for attempt in range(3):  # What is 3?
        response = requests.get(url, timeout=30)  # Why 30?
        if response.status_code == 200:
            return response.json()

テスト

テストには pytest を使用する
import pytest
from myapp.processor import DataProcessor

def test_process_valid_data():
    """Test processing with valid input."""
    processor = DataProcessor()
    result = processor.process([1, 2, 3])
    assert result == [2, 4, 6]

def test_process_empty_data():
    """Test processing with empty input."""
    processor = DataProcessor()
    with pytest.raises(ValueError):
        processor.process([])

@pytest.fixture
def sample_data():
    """Provide sample data for tests."""
    return [1, 2, 3, 4, 5]

def test_with_fixture(sample_data):
    """Test using fixture."""
    processor = DataProcessor()
    result = processor.process(sample_data)
    assert len(result) == len(sample_data)

要点

  1. PEP 8 のスタイルガイドラインに一貫して従う
  2. 関数シグネチャには必ず型ヒントを使用する
  3. すべての公開関数/クラスに NumPy スタイルの docstring を記述する
  4. 裸の except ではなく、具体的な例外型を使用する
  5. os.path より pathlib.Path を優先する
  6. 構造化データには dataclass と enum を使用する
  7. インポートを標準ライブラリ → サードパーティ → ローカルの順に整理する
  8. マジックナンバーを避け、名前付き定数を使用する
  9. pytest を使ってテストを記述する
  10. 最新の Python 構文を使用する(3.9+)

コメント