Tasuke Hubのロゴ

ITを中心に困っている人を助けるメディア

分かりやすく解決策を提供することで、あなたの困ったをサポート。 全ての人々がスムーズに生活できる世界を目指します。

Pythonでエラーハンドリングを徹底マスター!実践的なテストケースによる学び方を解説

記事のサムネイル
TH

Tasuke Hub管理人

東証プライム市場上場企業エンジニア

情報系修士卒業後、大手IT企業にてフルスタックエンジニアとして活躍。 Webアプリケーション開発からクラウドインフラ構築まで幅広い技術に精通し、 複数のプロジェクトでリードエンジニアを担当。 技術ブログやオープンソースへの貢献を通じて、日本のIT技術コミュニティに積極的に関わっている。

🎓情報系修士🏢東証プライム上場企業💻フルスタックエンジニア📝技術ブログ執筆者

更新履歴

2025/05/09: 記事の内容を大幅に見直しました。

おすすめの書籍

Pythonにおけるエラーハンドリングの基本

プログラミングでは「エラーとの戦い」が日常茶飯事です。特にPythonのような動的型付け言語では、コンパイル時ではなく実行時にエラーが発生することが多く、適切なエラーハンドリングが必須のスキルとなります。

Pythonでは、エラーハンドリングの基礎となるのが「例外処理」です。例外処理の基本的な構文は以下のようになります。

try:
    # エラーが発生する可能性のあるコード
    result = 10 / 0  # ゼロ除算エラーが発生
except ZeroDivisionError as e:
    # エラー発生時の処理
    print(f"エラーが発生しました: {e}")
finally:
    # エラーの有無にかかわらず実行される処理
    print("処理を終了します")

上記のコードでは、tryブロック内でゼロ除算を行っているためZeroDivisionErrorが発生します。このエラーはexceptブロックでキャッチされ、エラーメッセージが表示されます。そして最後にfinallyブロックが実行されます。

Pythonの主要な組み込み例外の一部を紹介します:

  • SyntaxError: 構文エラー
  • NameError: 未定義の変数にアクセスした場合
  • TypeError: 不適切な型の操作を試みた場合
  • ValueError: 適切な型だが不適切な値を関数に渡した場合
  • IndexError: リストの範囲外のインデックスにアクセスした場合
  • KeyError: 存在しないキーで辞書にアクセスした場合
  • FileNotFoundError: 存在しないファイルを開こうとした場合
  • ImportError: モジュールのインポートに失敗した場合

複数の例外を同時に処理したい場合は、以下のように書くことができます。

try:
    # 何らかの処理
    value = int(input("数値を入力してください: "))
    result = 100 / value
except ValueError:
    print("数値を入力してください")
except ZeroDivisionError:
    print("0以外の数値を入力してください")
except Exception as e:
    print(f"予期せぬエラーが発生しました: {e}")
else:
    # エラーが発生しなかった場合に実行される
    print(f"結果: {result}")

「失敗するかもしれない処理はtryで囲み、適切なexceptブロックで対応する」というシンプルな原則を覚えておくと良いでしょう。これがPythonにおけるエラーハンドリングの第一歩です。

あわせて読みたい

おすすめの書籍

実践で頻出するエラーパターンとその対処法

実際のPython開発では、特定のエラーパターンが頻繁に発生します。これらのエラーを理解し、適切に対処することが重要です。ここでは、よく遭遇するエラーパターンとその対処法を解説します。

1. 型関連のエラー

Pythonは動的型付け言語ですが、型に関するエラーは頻繁に発生します。

# TypeError: リストに整数を足そうとする
my_list = [1, 2, 3]
result = my_list + 4  # TypeError: can only concatenate list (not "int") to list

# 対処法
result = my_list + [4]  # 正しい: リストに別のリストを連結
# ValueError: 文字列を整数に変換しようとする
value = int("abc")  # ValueError: invalid literal for int() with base 10: 'abc'

# 対処法
try:
    value = int("abc")
except ValueError:
    value = 0  # デフォルト値を設定

2. 辞書とリストの操作

辞書やリストのキー・インデックスに関するエラーも多いです。

# KeyError: 存在しないキーにアクセス
data = {"name": "太郎", "age": 30}
email = data["email"]  # KeyError: 'email'

# 対処法1: get()メソッドでデフォルト値を指定
email = data.get("email", "メールアドレスなし")

# 対処法2: キーの存在確認
if "email" in data:
    email = data["email"]
else:
    email = "メールアドレスなし"
# IndexError: リストの範囲外アクセス
numbers = [1, 2, 3]
value = numbers[10]  # IndexError: list index out of range

# 対処法: インデックスの範囲チェック
if 10 < len(numbers):
    value = numbers[10]
else:
    value = None

3. ファイル操作のエラー

ファイル操作は多くのエラーを引き起こす可能性があります。

# FileNotFoundError: 存在しないファイルを開く
with open("存在しないファイル.txt", "r") as f:
    content = f.read()  # FileNotFoundError: [Errno 2] No such file or directory

# 対処法: try-exceptでエラーをキャッチ
try:
    with open("存在しないファイル.txt", "r") as f:
        content = f.read()
except FileNotFoundError:
    content = ""
    print("ファイルが見つかりませんでした")

4. ネットワーク関連のエラー

ネットワーク通信でも様々なエラーが発生します。

import requests

# 対処法: 様々なネットワークエラーに対応
try:
    response = requests.get("https://example.com/api")
    data = response.json()
except requests.exceptions.ConnectionError:
    print("接続エラーが発生しました")
except requests.exceptions.Timeout:
    print("リクエストがタイムアウトしました")
except requests.exceptions.RequestException as e:
    print(f"リクエストエラー: {e}")
except ValueError:  # JSONのパースエラー
    print("不正なJSONレスポンスです")

実際の開発では、これらのエラーパターンを認識し、必要に応じて適切なエラーハンドリングを行うことが重要です。「エラーが発生しそうな場所を予測し、適切に処理する」という思考プロセスを身につけることで、より堅牢なコードを書けるようになります。

おすすめの書籍

try-exceptの応用テクニック

Pythonのエラーハンドリングをマスターするためには、try-exceptの基本に加えて、いくつかの応用テクニックを知っておくと便利です。ここでは、実際の開発でよく使われる応用テクニックを紹介します。

1. 複数の例外を一度に処理する

複数の例外を同じ方法で処理したい場合は、タプルを使って複数の例外を指定できます。

try:
    # 何らかの処理
    value = int(input("数値を入力: "))
    result = 100 / value
except (ValueError, ZeroDivisionError):
    # ValueErrorとZeroDivisionErrorを同じ方法で処理
    print("有効な数値(0以外)を入力してください")

2. else節とfinally節の組み合わせ

else節とfinally節を組み合わせることで、エラー処理の流れをより細かく制御できます。

try:
    file = open("data.txt", "r")
    content = file.read()
except FileNotFoundError:
    print("ファイルが見つかりません")
    content = None
else:
    # エラーが発生しなかった場合のみ実行
    print(f"ファイルの内容: {content}")
    # ファイルの処理を続ける
finally:
    # エラーの有無にかかわらず実行される
    if 'file' in locals() and not file.closed:
        file.close()
    print("処理を終了します")

3. 例外の再発生(re-raising)

例外を処理した後、同じ例外を再度発生させることもできます。これは、ログを取りつつも、呼び出し元に例外を伝播させたい場合に便利です。

def process_data(data):
    try:
        # データ処理
        result = data["value"] / data["divisor"]
        return result
    except (KeyError, ZeroDivisionError) as e:
        # エラーログを記録
        log_error(f"データ処理エラー: {e}")
        # 例外を再発生
        raise

より高度な使い方として、別の例外に変換して再発生させることもできます。

def read_config(filename):
    try:
        with open(filename, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        # より具体的なカスタム例外に変換
        raise ConfigFileError(f"設定ファイル {filename} が見つかりません")
    except json.JSONDecodeError as e:
        # エラーメッセージを含む別の例外に変換
        raise ConfigParseError(f"設定ファイルの解析に失敗しました: {e}")

4. コンテキストマネージャを活用する

Pythonのwith文を使うことで、リソースの適切な開放を保証できます。

# ファイル操作の例
try:
    with open("data.txt", "r") as file:
        content = file.read()
        # ファイルは自動的に閉じられる
except FileNotFoundError:
    print("ファイルが見つかりません")

# データベース接続の例
import sqlite3
try:
    with sqlite3.connect("example.db") as conn:
        cursor = conn.cursor()
        cursor.execute("SELECT * FROM users")
        data = cursor.fetchall()
        # トランザクションは自動的にコミットされる
except sqlite3.Error as e:
    print(f"データベースエラー: {e}")
    # トランザクションは自動的にロールバックされる

5. 例外を使った制御フロー

例外はエラー処理だけでなく、特定の状況での制御フロー管理にも使えます。

def find_user(user_id, users):
    for user in users:
        if user["id"] == user_id:
            return user
    # ユーザーが見つからない場合は例外を発生
    raise ValueError(f"ユーザーID {user_id} が見つかりません")

# 使用例
try:
    user = find_user(5, user_list)
    # ユーザーが見つかった場合の処理
    process_user(user)
except ValueError as e:
    # ユーザーが見つからなかった場合の処理
    print(e)
    create_new_user(5)

「例外は例外的な状況のためのもの」という原則を念頭に置きつつ、これらの応用テクニックを適切に活用することで、より堅牢で読みやすいコードを書くことができます。

おすすめの書籍

関連記事

カスタム例外クラスの作成と活用

Pythonの標準例外クラスは多くの状況で十分ですが、より具体的なエラー状況を表現したい場合や、アプリケーション特有のエラーを扱いたい場合には、カスタム例外クラスの作成が有効です。カスタム例外を活用することで、コードの可読性と保守性が向上します。

カスタム例外クラスの基本

カスタム例外クラスは、既存の例外クラス(通常はException)を継承して作成します。

class MyCustomError(Exception):
    """基本的なカスタム例外"""
    pass

# 使用例
def some_function():
    if something_went_wrong:
        raise MyCustomError("エラーが発生しました")

シンプルなカスタム例外であれば、上記のような実装で十分です。ただし、より情報を提供するためのカスタム例外を作成することもできます。

情報を持つカスタム例外

エラーに関する追加情報を持つカスタム例外を作成することで、エラーハンドリングの精度が向上します。

class DatabaseError(Exception):
    """データベース操作に関するエラー"""
    def __init__(self, message, error_code=None, query=None):
        super().__init__(message)
        self.error_code = error_code
        self.query = query
        self.timestamp = datetime.now()
    
    def __str__(self):
        error_msg = super().__str__()
        if self.error_code:
            error_msg = f"[エラーコード: {self.error_code}] {error_msg}"
        return error_msg
        
# 使用例
try:
    # データベース操作
    result = execute_query("SELECT * FROM non_existent_table")
except SomeDBLibraryError as e:
    # ライブラリのエラーをカスタム例外に変換
    raise DatabaseError(
        f"テーブル操作エラー: {e}",
        error_code=e.code,
        query="SELECT * FROM non_existent_table"
    )

例外階層の構築

大規模なアプリケーションでは、関連するエラーをグループ化するために例外階層を作成すると便利です。

# 基本例外クラス
class AppError(Exception):
    """アプリケーション全体のベース例外"""
    pass

# サブシステム別の例外
class ConfigError(AppError):
    """設定関連のエラー"""
    pass

class DatabaseError(AppError):
    """データベース関連のエラー"""
    pass

class APIError(AppError):
    """API関連のエラー"""
    pass

# さらに具体的な例外
class ConfigFileNotFoundError(ConfigError):
    """設定ファイルが見つからない"""
    pass

class ConfigParseError(ConfigError):
    """設定ファイルの解析エラー"""
    pass

# 使用例
try:
    # アプリケーションの操作
    do_something()
except ConfigError as e:
    # 設定関連のエラー処理
    handle_config_error(e)
except DatabaseError as e:
    # データベース関連のエラー処理
    handle_database_error(e)
except AppError as e:
    # その他のアプリケーションエラー処理
    handle_general_app_error(e)

この階層構造により、特定のエラーを詳細に捕捉したり、より一般的なエラーとしてまとめて処理したりできます。

カスタム例外の適切な使用場面

カスタム例外を作成する一般的なケースには、以下のようなものがあります:

  1. ドメイン固有のエラー:例えば、ユーザー認証失敗にはAuthenticationError、許可されていない操作にはPermissionDeniedErrorなど

  2. 処理フローの制御:通常のエラーではなく、特定の状況を示すための例外(StopIterationのように)

  3. 外部ライブラリのエラーを変換:外部ライブラリの例外を、アプリケーション固有の例外に変換して一貫性を保つ

import requests

class APIConnectionError(Exception):
    """API接続エラー"""
    pass

class APIResponseError(Exception):
    """APIレスポンスエラー"""
    pass

def fetch_data_from_api(url):
    try:
        response = requests.get(url, timeout=5)
        response.raise_for_status()  # 4xx/5xxエラーを例外として発生
        return response.json()
    except requests.exceptions.ConnectionError:
        raise APIConnectionError(f"API {url} への接続に失敗しました")
    except requests.exceptions.HTTPError as e:
        raise APIResponseError(f"APIエラー: {e}")
    except requests.exceptions.Timeout:
        raise APIConnectionError(f"API {url} へのリクエストがタイムアウトしました")
    except ValueError:  # JSONのパースエラー
        raise APIResponseError("不正なJSON形式のレスポンスです")

カスタム例外を適切に活用することで、コードの意図が明確になり、エラー処理が一貫した形で行えるようになります。しかし、過剰なカスタム例外の作成は避け、必要な場合にのみ使用することをお勧めします。

おすすめの書籍

テストケースを使ったエラーハンドリングの検証

エラーハンドリングの実装は、正常系(正常に動作する場合)のテストだけでなく、異常系(エラーが発生する場合)のテストも重要です。テストケースを使ってエラーハンドリングを検証することで、コードの信頼性が大幅に向上します。ここでは、Pythonの標準ライブラリunittestと人気のあるテストフレームワークpytestを使ったエラーハンドリングのテスト方法を紹介します。

unittestを使ったエラーハンドリングのテスト

Pythonの標準ライブラリunittestを使用すると、例外が適切に発生するかどうかをテストできます。

import unittest

# テスト対象の関数
def divide(a, b):
    if b == 0:
        raise ValueError("ゼロ除算はできません")
    return a / b

class TestDivideFunction(unittest.TestCase):
    def test_normal_division(self):
        # 正常系のテスト
        result = divide(10, 2)
        self.assertEqual(result, 5)
    
    def test_zero_division(self):
        # 例外が発生することを検証
        with self.assertRaises(ValueError) as context:
            divide(10, 0)
        
        # 例外メッセージの検証
        self.assertEqual(str(context.exception), "ゼロ除算はできません")
        
if __name__ == '__main__':
    unittest.main()

assertRaisesコンテキストマネージャを使用することで、特定の例外が発生することを検証できます。また、context.exceptionを使って例外オブジェクトにアクセスし、例外メッセージなどの詳細を検証することも可能です。

pytestを使ったエラーハンドリングのテスト

pytestは、より簡潔な文法でテストを書くことができるフレームワークです。例外のテストも直感的に行えます。

import pytest

# テスト対象の関数
def read_config(filename):
    try:
        with open(filename, 'r') as f:
            return f.read()
    except FileNotFoundError:
        raise ConfigError(f"設定ファイル '{filename}' が見つかりません")

class ConfigError(Exception):
    pass

def test_read_config_normal():
    # 正常系のテスト(ファイルが存在する場合)
    # モックやテンポラリファイルを使用することが多い
    assert read_config("existing_file.txt") == "設定内容"

def test_read_config_file_not_found():
    # 例外が発生することを検証
    with pytest.raises(ConfigError) as excinfo:
        read_config("non_existent_file.txt")
    
    # 例外メッセージの検証
    assert "見つかりません" in str(excinfo.value)

pytest.raisesコンテキストマネージャはunittest.assertRaisesと同様に機能しますが、より簡潔な文法で使用できます。

実際のテストケース例

以下は、データ処理関数のエラーハンドリングをテストする実際の例です。

import pytest
import json
from myapp.data_processor import process_user_data, UserDataError, InvalidFormatError

# テスト用のサンプルデータ
valid_data = {'name': '山田太郎', 'age': 30, 'email': '[email protected]'}
invalid_format_data = "不正なJSONデータ"
missing_field_data = {'name': '山田太郎'}  # ageフィールドが欠けている

def test_process_user_data_valid():
    # 正常系のテスト
    result = process_user_data(valid_data)
    assert result['processed'] == True
    assert 'error' not in result

def test_process_user_data_invalid_format():
    # 不正なフォーマットの場合のテスト
    with pytest.raises(InvalidFormatError) as excinfo:
        process_user_data(invalid_format_data)
    
    assert "データフォーマットが不正です" in str(excinfo.value)

def test_process_user_data_missing_field():
    # 必須フィールドが欠けている場合のテスト
    with pytest.raises(UserDataError) as excinfo:
        process_user_data(missing_field_data)
    
    assert "必須フィールド 'age' がありません" in str(excinfo.value)

def test_process_user_data_handles_unexpected_error(mocker):
    # 予期せぬエラーが適切に処理されるかテスト
    mocker.patch('myapp.data_processor.validate_user', side_effect=Exception("予期せぬエラー"))
    
    with pytest.raises(UserDataError) as excinfo:
        process_user_data(valid_data)
    
    assert "データ処理中にエラーが発生しました" in str(excinfo.value)

このようなテストを作成することで、様々なエラー状況でコードが期待通りに動作することを確認できます。特に、境界条件や異常系のテストは、バグを早期に発見するのに役立ちます。

テスト駆動開発 (TDD) とエラーハンドリング

テスト駆動開発(TDD)のアプローチを使用すると、エラーハンドリングの実装がより体系的になります。

  1. まず、発生する可能性のあるエラーケースのテストを書く
  2. テストが失敗することを確認(RED)
  3. エラーハンドリングを実装してテストが通るようにする(GREEN)
  4. コードをリファクタリング(REFACTOR)

例えば、以下のようなテストから始めます:

def test_user_authentication_invalid_credentials():
    with pytest.raises(AuthenticationError) as excinfo:
        authenticate_user("invalid_user", "wrong_password")
    
    assert "ユーザー名またはパスワードが正しくありません" in str(excinfo.value)

このテストに基づいて、実装を進めていきます:

def authenticate_user(username, password):
    user = find_user(username)
    if not user:
        raise AuthenticationError("ユーザー名またはパスワードが正しくありません")
    
    if not verify_password(user, password):
        raise AuthenticationError("ユーザー名またはパスワードが正しくありません")
    
    return create_session(user)

このアプローチにより、全てのエラーケースが適切に処理されることを保証できます。

適切なテストケースを使ってエラーハンドリングを検証することで、より堅牢で信頼性の高いコードを作成できます。これは、特に複雑なシステムやプロダクション環境で使用されるコードにとって非常に重要です。

おすすめの書籍

エラーハンドリングのベストプラクティス

Pythonでのエラーハンドリングをより効果的に行うために、以下のベストプラクティスを心がけましょう。これらの原則は、実際の開発経験から得られた知見に基づいています。

1. 具体的な例外をキャッチする

可能な限り、Exceptionのような一般的な例外ではなく、具体的な例外クラスをキャッチしましょう。これにより、コードの意図が明確になり、予期せぬエラーを見落とす可能性が減ります。

# 良い例
try:
    # ファイル操作
    with open("config.txt", "r") as f:
        config = f.read()
except FileNotFoundError:
    # 具体的なエラー処理
    print("設定ファイルが見つかりません")
    config = default_config()

# 避けるべき例
try:
    # ファイル操作
    with open("config.txt", "r") as f:
        config = f.read()
except Exception:  # 全ての例外をキャッチしてしまう
    # 具体的な原因がわからない
    print("エラーが発生しました")
    config = default_config()

ただし、複数の例外を同じ方法で処理したい場合や、ログを記録した上で再発生させる場合など、Exceptionをキャッチすることが適切な場面もあります。

2. 例外を握りつぶさない

例外をキャッチした後、何もしない(「例外を握りつぶす」)ことは避けましょう。少なくともログを記録するか、より適切な例外に変換するか、処理を続行するのに必要な代替策を実行しましょう。

# 避けるべき例
try:
    result = perform_calculation()
except ZeroDivisionError:
    pass  # 何もしない(例外を握りつぶす)

# 良い例
try:
    result = perform_calculation()
except ZeroDivisionError:
    logger.warning("ゼロ除算が発生しました - デフォルト値を使用します")
    result = 0  # 適切な代替値を設定

3. try節はできるだけ小さく保つ

tryブロック内のコードは、必要最小限に保ちましょう。これにより、どの行でエラーが発生したかを特定しやすくなります。

# 避けるべき例
try:
    data = load_data()
    result = process_data(data)
    save_result(result)
    notify_user()
except Exception as e:
    logger.error(f"処理中にエラーが発生しました: {e}")

# 良い例
try:
    data = load_data()
except IOError as e:
    logger.error(f"データの読み込みに失敗しました: {e}")
    return

try:
    result = process_data(data)
except ValueError as e:
    logger.error(f"データの処理に失敗しました: {e}")
    return

# 以下同様...

4. エラーメッセージは具体的かつ有用に

例外を発生させる際は、問題の原因と考えられる解決策を含む具体的なエラーメッセージを提供しましょう。

# 避けるべき例
if not is_valid_email(email):
    raise ValueError("不正なメールアドレス")

# 良い例
if not is_valid_email(email):
    raise ValueError(f"'{email}' は有効なメールアドレス形式ではありません。[email protected] の形式で入力してください")

5. デバッグ情報を含める

例外オブジェクトに、デバッグに役立つ情報を含めることを検討しましょう。カスタム例外クラスを使用すると、追加情報を簡単に含めることができます。

class DatabaseError(Exception):
    def __init__(self, message, query=None, error_code=None):
        super().__init__(message)
        self.query = query
        self.error_code = error_code
        self.timestamp = datetime.now()

# 使用例
try:
    # データベース操作
except Exception as e:
    raise DatabaseError(
        "データベース接続エラー", 
        query="SELECT * FROM users", 
        error_code=e.args[0]
    )

6. ログと例外処理を組み合わせる

例外処理とロギングを組み合わせることで、問題の診断と解決が容易になります。

import logging
logger = logging.getLogger(__name__)

try:
    process_data()
except ValueError as e:
    logger.error(f"データ処理エラー: {e}", exc_info=True)
    # 必要に応じて処理を続行または中断
except Exception as e:
    logger.critical(f"予期せぬエラー: {e}", exc_info=True)
    # 安全に処理を中断
    raise

exc_info=Trueを指定すると、スタックトレースも含めて記録されます。

7. 例外の文脈を維持する

Python 3では、例外の再発生時に元の例外の情報が保持されます。また、fromキーワードを使用して、例外の連鎖を明示的に表現できます。

try:
    # 何らかの処理
except SomeError as e:
    # 元の例外からの情報を保持
    raise CustomError("より高レベルのエラーメッセージ") from e

8. finallyブロックを適切に使用する

リソースの解放やクリーンアップ処理は、finallyブロックに配置することで、例外の有無にかかわらず確実に実行されます。ただし、可能な限りwith文のようなコンテキストマネージャを使用することをお勧めします。

file = None
try:
    file = open("data.txt", "r")
    # ファイル処理
finally:
    if file and not file.closed:
        file.close()

# 上記よりもこちらがベター
with open("data.txt", "r") as file:
    # ファイル処理

9. アサーションを適切に使用する

アサーション(assert文)は、プログラムの前提条件を検証するのに役立ちます。ただし、ユーザー入力の検証には使用せず、内部的な整合性チェックに限定しましょう。

def calculate_average(numbers):
    # 内部的な前提条件の検証
    assert isinstance(numbers, list), "リストが必要です"
    assert numbers, "空のリストは処理できません"
    
    return sum(numbers) / len(numbers)

注意:Python実行時に-O(最適化)フラグを使用すると、アサーションは無視されます。そのため、重要なチェックには例外を使用しましょう。

10. エラーの回復と再試行のメカニズム

一時的なエラーの場合は、再試行のメカニズムを実装することを検討しましょう。

def fetch_data_with_retry(url, max_retries=3, delay=1):
    import time
    
    for attempt in range(max_retries):
        try:
            return requests.get(url).json()
        except (requests.ConnectionError, requests.Timeout) as e:
            if attempt < max_retries - 1:  # 最後の試行以外
                time.sleep(delay)  # 再試行前に待機
                delay *= 2  # 指数バックオフ
                continue
            raise  # 最大再試行回数に達したら例外を再発生

「エラーを防ぐよりも検出するほうが簡単」というPythonの哲学を念頭に置きつつ、これらのベストプラクティスを実践することで、より堅牢で保守しやすいコードを書くことができます。

"エラーに対処するのではなく、エラーを防ぐように努めよう。それでもエラーが発生したら、素早く失敗し、明確なメッセージを伝えよう。" - Pythonの格言

おすすめの書籍

おすすめ記事

おすすめコンテンツ