例外処理とロギング

PythonPythonBeginner
今すぐ練習

This tutorial is from open-source community. Access the source code

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

この実験では、Python で例外処理を実装する方法を学びます。デバッグやコードの保守に不可欠な、より良いエラー報告のためにロギングモジュールを使用する方法を理解します。

また、reader.py ファイルを修正して、クラッシュする代わりにエラーを適切に処理する方法を練習します。この実践的な経験は、堅牢な Python プログラムを書く能力を向上させます。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ControlFlowGroup -.-> python/for_loops("For Loops") python/ModulesandPackagesGroup -.-> python/standard_libraries("Common Standard Libraries") python/ErrorandExceptionHandlingGroup -.-> python/catching_exceptions("Catching Exceptions") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") subgraph Lab Skills python/conditional_statements -.-> lab-132507{{"例外処理とロギング"}} python/for_loops -.-> lab-132507{{"例外処理とロギング"}} python/standard_libraries -.-> lab-132507{{"例外処理とロギング"}} python/catching_exceptions -.-> lab-132507{{"例外処理とロギング"}} python/raising_exceptions -.-> lab-132507{{"例外処理とロギング"}} end

Python での例外の理解

このステップでは、Python の例外について学びます。例外はプログラミングにおける重要な概念です。プログラムの実行中に発生する予期しない状況を処理するのに役立ちます。また、現在のコードが無効なデータを処理しようとするとクラッシュする理由を明らかにします。これを理解することで、より堅牢で信頼性の高い Python プログラムを書くことができます。

例外とは何か?

Python では、例外とはプログラムの実行中に発生し、通常の命令の流れを妨げるイベントです。高速道路上の障害物のようなものです。すべてが順調に進むときは、プログラムは決まった経路をたどります。これは、空いた道路を走る車のようなものです。しかし、エラーが発生すると、Python は例外オブジェクトを作成します。このオブジェクトは、何がうまくいかなかったかに関する情報(エラーの種類やコード内での発生場所など)を含むレポートのようなものです。

これらの例外が適切に処理されないと、プログラムはクラッシュします。クラッシュが発生すると、Python はトレースバックメッセージを表示します。このメッセージは、エラーが発生したコード内の正確な位置を示す地図のようなもので、デバッグに非常に役立ちます。

現在のコードを調べる

まず、reader.py ファイルの構造を見てみましょう。このファイルには、CSV データを読み取り、変換するための関数が含まれています。エディタでファイルを開くには、正しいディレクトリに移動する必要があります。ターミナルで cd コマンドを使用します。

cd /home/labex/project

これで正しいディレクトリに移動したので、reader.py の内容を見てみましょう。このファイルにはいくつかの重要な関数があります。

  1. convert_csv(): この関数はデータの行を受け取り、指定された変換関数を使用してそれらを変換します。原材料(データ行)を受け取り、特定のレシピ(変換関数)に従って別の形に変える機械のようなものです。
  2. csv_as_dicts(): この関数は CSV データを読み取り、辞書のリストに変換します。また、型変換も行います。つまり、辞書内の各データが文字列、整数、または浮動小数点数などの正しい型であることを確認します。
  3. read_csv_as_dicts(): これはラッパー関数です。csv_as_dicts() 関数を呼び出して作業を行うマネージャーのようなものです。

問題を実演する

コードが無効なデータを処理しようとしたときに何が起こるか見てみましょう。Python インタープリタを開きます。これは、Python コードを対話的にテストできる遊び場のようなものです。Python インタープリタを開くには、ターミナルで次のコマンドを使用します。

python3

Python インタープリタが開いたら、missing.csv ファイルを読み取ろうとします。このファイルにはいくつかの欠落または無効なデータが含まれています。reader.py ファイルの read_csv_as_dicts() 関数を使用してデータを読み取ります。

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])

このコードを実行すると、次のようなエラーメッセージが表示されるはずです。

Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: ''

このエラーは、コードが空の文字列を整数に変換しようとするために発生します。空の文字列は有効な整数を表さないため、Python は変換を行うことができません。関数は最初に遭遇したエラーでクラッシュし、ファイル内の残りの有効なデータの処理を停止します。

Python インタープリタを終了するには、次のコマンドを入力します。

exit()

エラーの流れを理解する

エラーは convert_csv() 関数内、具体的には次の行で発生します。

return list(map(lambda row: converter(headers, row), rows))

map() 関数は、rows リスト内の各行に converter 関数を適用します。converter 関数は、各行に型(str、int、float)を適用しようとします。しかし、欠落したデータが含まれる行に遭遇すると失敗します。map() 関数には例外を処理する組み込みの方法がないため、例外が発生すると、処理全体がクラッシュします。

次のステップでは、これらの例外を適切に処理するようにコードを修正します。つまり、クラッシュする代わりに、プログラムはエラーを処理し、残りのデータの処理を続けることができるようになります。

例外処理の実装

このステップでは、コードをより堅牢にすることに焦点を当てます。プログラムが不適切なデータに遭遇すると、しばしばクラッシュします。しかし、例外処理と呼ばれる手法を使って、これらの問題を適切に処理することができます。reader.py ファイルを修正してこれを実装します。例外処理により、プログラムは予期しないデータに直面しても、突然停止する代わりに実行を続けることができます。

try-except ブロックの理解

Python は、try-except ブロックを使って例外を処理する強力な方法を提供しています。その仕組みを解説します。

try:
    ## Code that might cause an exception
    result = risky_operation()
except SomeExceptionType as e:
    ## Code that runs if the exception occurs
    handle_exception(e)

try ブロックには、例外を引き起こす可能性のあるコードを記述します。例外とは、プログラムの実行中に発生するエラーです。たとえば、数をゼロで割ろうとすると、Python は ZeroDivisionError 例外を発生させます。try ブロック内で例外が発生すると、Python は try ブロック内のコードの実行を停止し、一致する except ブロックにジャンプします。except ブロックには、例外を処理するコードが含まれています。SomeExceptionType は、キャッチしたい例外の型です。特定の型の例外をキャッチすることも、一般的な Exception を使ってすべての型の例外をキャッチすることもできます。as e の部分で、エラーに関する情報を含む例外オブジェクトにアクセスできます。

コードの修正

では、try-except ブロックについて学んだことを convert_csv() 関数に適用しましょう。エディタで reader.py ファイルを開きます。

  1. 現在の convert_csv() 関数を次のコードに置き換えます。
def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Print a warning message for bad rows
            print(f"Row {row_idx}: Bad row: {row}")
            continue

    return result

この新しい実装では、以下のことを行っています。

  • map() の代わりに for ループを使って各行を処理します。これにより、各行の処理をより細かく制御できます。
  • 変換コードを try-except ブロックで囲みます。これは、行の変換中に例外が発生しても、プログラムがクラッシュしないことを意味します。代わりに、except ブロックにジャンプします。
  • except ブロックでは、無効な行に対するエラーメッセージを出力します。これにより、どの行に問題があるかを特定できます。
  • エラーメッセージを出力した後、continue 文を使って現在の行をスキップし、残りの行の処理を続けます。

これらの変更を加えた後、ファイルを保存します。

変更のテスト

修正したコードを missing.csv ファイルでテストしましょう。まず、ターミナルで次のコマンドを実行して Python インタープリタを開きます。

python3

Python インタープリタに入ったら、次のコードを実行します。

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

このコードを実行すると、問題のある各行に対するエラーメッセージが表示されるはずです。ただし、プログラムは処理を続け、有効な行を返します。以下は、表示される可能性のある出力の例です。

Row 4: Bad row: ['C', '', '53.08']
Row 7: Bad row: ['DIS', '50', 'N/A']
Row 8: Bad row: ['GE', '', '37.23']
Row 13: Bad row: ['INTC', '', '21.84']
Row 17: Bad row: ['MCD', '', '51.11']
Row 19: Bad row: ['MO', '', '70.09']
Row 22: Bad row: ['PFE', '', '26.40']
Row 26: Bad row: ['VZ', '', '42.92']
Number of valid rows processed: 20

また、有効なデータでプログラムが正しく動作することも確認しましょう。Python インタープリタで次のコードを実行します。

valid_port = read_csv_as_dicts('valid.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(valid_port)}")

すべての行がエラーなく処理されることが確認できるはずです。以下は、出力の例です。

Number of valid rows processed: 17

Python インタープリタを終了するには、次のコマンドを実行します。

exit()

これで、コードはより堅牢になりました。不適切な行をスキップすることで、無効なデータを適切に処理できるようになり、クラッシュすることがなくなります。これにより、プログラムはより信頼性が高く、使いやすくなります。

✨ 解答を確認して練習

ロギングの実装

このステップでは、コードを改善します。単純な print メッセージを使用する代わりに、Python の logging モジュールを使用して適切なロギングを行います。ロギングは、プログラムが何をしているかを追跡する素晴らしい方法です。特に、エラーの処理やコードの流れを理解する際に役立ちます。

ロギングモジュールの理解

Python の logging モジュールは、アプリケーションからログメッセージを送信する柔軟な方法を提供します。単純な print 文を使用するよりもはるかに強力です。以下はその機能です。

  1. 異なるログレベル (DEBUG、INFO、WARNING、ERROR、CRITICAL): これらのレベルは、メッセージの重要度を分類するのに役立ちます。たとえば、DEBUG は開発中に役立つ詳細情報用で、CRITICAL はプログラムを停止させる可能性のある深刻なエラー用です。
  2. 設定可能な出力形式: ログメッセージの外見を決定できます。たとえば、タイムスタンプやその他の有用な情報を追加することができます。
  3. メッセージを異なる出力先 (コンソール、ファイルなど) に送信できる: ログメッセージをコンソールに表示したり、ファイルに保存したり、リモートサーバーに送信したりすることができます。
  4. 重大度に基づくログフィルタリング: ログレベルに基づいて表示するメッセージを制御できます。

reader.py にロギングを追加する

では、コードを変更してロギングモジュールを使用しましょう。reader.py ファイルを開きます。

まず、logging モジュールをインポートし、このモジュール用のロガーを設定する必要があります。ファイルの先頭に次のコードを追加します。

import logging

## Set up a logger for this module
logger = logging.getLogger(__name__)

import logging 文は logging モジュールをインポートし、その関数を使用できるようにします。logging.getLogger(__name__) は、この特定のモジュール用のロガーを作成します。__name__ を使用することで、ロガーがモジュールに関連する一意の名前を持つことが保証されます。

次に、convert_csv() 関数を変更して、print 文の代わりにロギングを使用します。以下は更新されたコードです。

def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Log a warning message for bad rows
            logger.warning(f"Row {row_idx}: Bad row: {row}")
            ## Log the reason at debug level
            logger.debug(f"Row {row_idx}: Reason: {str(e)}")
            continue

    return result

主な変更点は以下の通りです。

  • エラーメッセージの print()logger.warning() に置き換えました。これにより、メッセージは適切な警告レベルでログに記録され、後でその可視性を制御できます。
  • 例外の詳細を含む新しい logger.debug() メッセージを追加しました。これにより、何がうまくいかなかったかに関するより多くの情報が得られますが、ログレベルが DEBUG またはそれ以下に設定されている場合にのみ表示されます。
  • str(e) は例外を文字列に変換するため、ログメッセージにエラーの理由を表示できます。

これらの変更を加えた後、ファイルを保存します。

ロギングのテスト

ロギングを有効にしてコードをテストしましょう。ターミナルで次のコマンドを実行して Python インタープリタを開きます。

python3

Python インタープリタに入ったら、次のコードを実行します。

import logging
import reader

## Configure logging level to see all messages
logging.basicConfig(level=logging.DEBUG)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

ここでは、まず logging モジュールと reader モジュールをインポートします。次に、logging.basicConfig(level=logging.DEBUG) を使用してログレベルを DEBUG に設定します。これは、DEBUG、INFO、WARNING、ERROR、CRITICAL を含むすべてのログメッセージが表示されることを意味します。その後、reader モジュールの read_csv_as_dicts 関数を呼び出し、処理された有効な行の数を表示します。

次のような出力が表示されるはずです。

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'
...
Number of valid rows processed: 20

ロギングモジュールが各メッセージにプレフィックスを追加し、ログレベル (WARNING/DEBUG) とモジュール名が表示されることに注意してください。

では、ログレベルを変更して警告のみを表示する場合を見てみましょう。Python インタープリタで次のコードを実行します。

## Reset the logging configuration
import logging
logging.basicConfig(level=logging.WARNING)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])

今回は、logging.basicConfig(level=logging.WARNING) を使用してログレベルを WARNING に設定します。これで、WARNING メッセージのみが表示され、DEBUG メッセージは非表示になります。

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
...

これは、異なるログレベルを使用する利点を示しています。コードを変更することなく、ログに表示される詳細度を制御できます。

Python インタープリタを終了するには、次のコマンドを実行します。

exit()

おめでとうございます!Python プログラムに適切な例外処理とロギングを実装しました。これにより、コードがより信頼性が高くなり、エラーが発生したときにより良い情報が得られます。

まとめ

この実験では、Python の例外処理とロギングに関するいくつかの重要な概念を学びました。まず、データ処理中に例外がどのように発生するかを理解し、try-except ブロックを実装して適切に処理する方法を学びました。また、エラーが発生した場合でも有効なデータの処理を続けるようにコードを修正しました。

次に、Python のロギングモジュールと、print 文に比べたその利点について学びました。WARNING や DEBUG などの異なるログレベルを実装し、異なる詳細度のロギングを設定する方法を確認しました。これらのスキルは、特にエラーが発生しやすい外部データを扱う場合、無人で動作するアプリケーションを構築する場合、または診断情報が必要なシステムを開発する場合に、堅牢な Python アプリケーションを作成するために不可欠です。これらの技術を Python プロジェクトに適用することで、信頼性と保守性を向上させることができます。