関数を使った大規模なプログラムの整理

Intermediate

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

はじめに

プログラムが大きくなり始めると、整理をしたいようになります。このセクションでは、関数とライブラリモジュールについて簡単に紹介します。また、例外によるエラー処理についても紹介します。

これは Guided Lab です。学習と実践を支援するためのステップバイステップの指示を提供します。各ステップを完了し、実践的な経験を積むために、指示に注意深く従ってください。過去のデータによると、この 中級 レベルの実験の完了率は 74%です。学習者から 100% の好評価を得ています。

カスタム関数

再利用したいコードには関数を使用します。以下は関数の定義です。

def sumcount(n):
    '''
    最初の n 個の整数の合計を返す
    '''
    total = 0
    while n > 0:
        total += n
        n -= 1
    return total

関数を呼び出すには、

a = sumcount(100)

関数は、何らかのタスクを実行して結果を返す一連の文です。関数の戻り値を明示的に指定するには、return キーワードが必要です。

ライブラリ関数

Python には大きな標準ライブラリが付属しています。ライブラリモジュールは import を使用してアクセスします。たとえば:

import math
x = math.sqrt(10)

import urllib.request
u = urllib.request.urlopen('http://www.python.org/')
data = u.read()

後でライブラリとモジュールについて詳しく説明します。

エラーと例外

関数は例外としてエラーを報告します。例外は関数を中止させ、処理されない場合にはプログラム全体を停止させる可能性があります。

Python の REPL でこれを試してみてください。

>>> int('N/A')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'N/A'
>>>

デバッグ目的で、メッセージには何が起こったか、エラーが発生した場所、および失敗に至った他の関数呼び出しを示すトレースバックが記載されています。

例外のキャッチと処理

例外をキャッチして処理することができます。

キャッチするには、try - except 文を使用します。

for line in file:
    fields = line.split(',')
    try:
        shares = int(fields[1])
    except ValueError:
        print("Couldn't parse", line)
  ...

ValueError という名前は、キャッチしようとしているエラーの種類と一致する必要があります。

実行中の操作に応じて、事前にどのような種類のエラーが発生するかを正確に把握することはしばしば困難です。良くも悪くも、例外処理はしばしばプログラムが予期せずクラッシュした後に追加されます(つまり、「ああ、そのエラーをキャッチするのを忘れていました。それを処理する必要があります!」)。

例外の発生

例外を発生させるには、raise 文を使用します。

raise RuntimeError('What a kerfuffle')

これにより、例外のトレースバック付きでプログラムが中止されます。try-except ブロックによってキャッチされない限りです。

% python3 foo.py
Traceback (most recent call last):
  File "foo.py", line 21, in <module>
    raise RuntimeError("What a kerfuffle")
RuntimeError: What a kerfuffle

演習 1.29:関数の定義

簡単な関数を定義してみましょう:

>>> def greeting(name):
        'Issues a greeting'
        print('Hello', name)

>>> greeting('Guido')
Hello Guido
>>> greeting('Paula')
Hello Paula
>>>

関数の最初の文が文字列の場合、それはドキュメントとして機能します。help(greeting) のようなコマンドを入力して、表示されるかどうかを確認してみてください。

演習 1.30:スクリプトを関数に変換する

演習 1.27 で pcost.py プログラムとして書いたコードを取り出し、関数 portfolio_cost(filename) に変換しましょう。この関数はファイル名を入力として受け取り、そのファイル内のポートフォリオデータを読み取り、ポートフォリオの合計コストを浮動小数点数として返します。

関数を使用するには、プログラムを変更して以下のようになるようにします:

## pcost.py
def portfolio_cost(filename):
    """
    Computes the total cost (shares*price) of a portfolio file
    """
    total_cost = 0.0

    with open(filename, "rt") as f:
        rows = f.readlines()
        headers = rows[0].strip().split(",")
        for row in rows[1:]:
            row_data = row.strip().split(",")
            nshares = int(row_data[1])
            price = float(row_data[2])
            total_cost += nshares * price

    return total_cost


import sys

if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = input("Enter a filename:")

cost = portfolio_cost(filename)
print("Total cost:", cost)

プログラムを実行すると、以前と同じ出力が表示されるはずです。プログラムを実行した後は、次のように入力することで対話的に関数を呼び出すこともできます:

$ python3 -i pcost.py

これにより、対話モードから関数を呼び出すことができます。

>>> portfolio_cost('portfolio.csv')
44671.15
>>>

コードを対話的に試すことができることは、テストとデバッグに役立ちます。

演習 1.31:エラー処理

欠落したフィールドがあるファイルに対して関数を試した場合、何が起こりますか?

>>> portfolio_cost('missing.csv')
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "pcost.py", line 11, in portfolio_cost
    nshares    = int(fields[1])
ValueError: invalid literal for int() with base 10: ''
>>>

この時点で、あなたは選択を迫られます。プログラムを動作させるには、不良な行を削除することで元の入力ファイルをクリーンアップするか、またはコードを修正して不良な行を何らかの方法で処理することができます。

pcost.py プログラムを修正して、例外をキャッチし、警告メッセージを表示し、その後ファイルの残りの部分を処理し続けるようにしましょう。

演習 1.32:ライブラリ関数の使用

Python には便利な関数が多数含まれる大きな標準ライブラリがあります。ここで役立つかもしれないライブラリの 1 つは、csv モジュールです。CSV データファイルを扱う必要があるときはいつでもこれを使用する必要があります。以下はその使い方の例です:

>>> import csv
>>> f = open('portfolio.csv')
>>> rows = csv.reader(f)
>>> headers = next(rows)
>>> headers
['name','shares', 'price']
>>> for row in rows:
        print(row)

['AA', '100', '32.20']
['IBM', '50', '91.10']
['CAT', '150', '83.44']
['MSFT', '200', '51.23']
['GE', '95', '40.37']
['MSFT', '50', '65.10']
['IBM', '100', '70.44']
>>> f.close()
>>>

csv モジュールの良いところの 1 つは、引用符の処理や適切なカンマ区切りなど、さまざまな低レベルの詳細を扱ってくれることです。上記の出力では、第 1 列の名前から二重引用符が取り除かれていることに気付くでしょう。

pcost.py プログラムを修正して、解析に csv モジュールを使用するようにして、以前の例を実行してみましょう。

演習 1.33:コマンドラインからの読み取り

pcost.py プログラムでは、入力ファイルの名前がコードに直接埋め込まれています:

## pcost.py

def portfolio_cost(filename):
 ...
    ## ここにコードを記述
 ...

cost = portfolio_cost('portfolio.csv')
print('Total cost:', cost)

学習やテストには問題ありませんが、本番のプログラムではおそらくそうはしません。

代わりに、ファイル名を引数としてスクリプトに渡すことができます。プログラムの下部を次のように変更してみてください:

## pcost_1.33.py

import csv


def portfolio_cost(filename):
    """
    Computes the total cost (shares*price) of a portfolio file
    """
    total_cost = 0.0

    with open(filename, "rt") as f:
        rows = csv.reader(f)
        headers = next(rows)  ## ヘッダー行をスキップ
        for row in rows:
            if len(row) < 3:
                print("Skipping invalid row:", row)
                continue
            try:
                nshares = int(row[1])
                price = float(row[2])
                total_cost += nshares * price
            except (IndexError, ValueError):
                print("Skipping invalid row:", row)

    return total_cost

import sys


if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    filename = 'portfolio.csv'

cost = portfolio_cost(filename)
print('Total cost:', cost)

sys.argv は、コマンドライン上で渡された引数(あれば)を含むリストです。

プログラムを実行するには、ターミナルから Python を実行する必要があります。

たとえば、Unix の bash から:

$ python3 pcost.py portfolio.csv
Total cost: 44671.15
bash %

まとめ

おめでとうございます!関数の実験を完了しました。スキルを向上させるために、LabEx でさらに多くの実験を行って練習してください。