開発するアプリケーションの概要
まずはHello Pythonです。いわゆる”Hello World!”と画面に表示されておしまいというわけではありません。基礎の基礎ではありますが、一通りの要素を含んだものです。
このアプリケーションは、CLI(コマンドラインインターフェース)から操作するもので、実行時にファイル名を指定します。指定したファイルからデータを読み取り、そのデータを処理した結果を画面に表示するものです。
具体的には、1 + 1と記載しておくと、それ読み取って結果を2と出すというものです。ファイルにはこの計算式を複数行記載し、連続して複数の計算結果を得られるようにします。
プログラミング的な要素としては、以下を含んでいます。
- 引数処理
- ファイル読み込み
- ループ
- 文字列操作
- 例外処理
完成形のソースコード
少しずつ順を追って実装しその過程を説明していきますが、ひとまず完成形のソースコードを掲載します。
hello_python.py
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': try: argv = sys.argv if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close except FileNotFoundError as e: print("[Error1]指定されたファイルが存在しません") print(e) quit() except Exception as e: print("[Error1]その他のエラーが発生しました") print(e) quit() for line in lines: try: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2]) if operator == "+": result = x + y print("result: " + str(result)) elif operator == "-": result = x - y print("result: " + str(result)) elif operator == "*": result = x * y print("result: " + str(result)) elif operator == "/": result = x / y print("result: " + str(result)) else: raise Exception("operator is not valid") except ValueError as e: print("[Error2]使用可能な値は整数です") print(e) except ZeroDivisionError as e: print("[Error2]ゼロで割ることは出来ません") print(e) except Exception as e: print("[Error2]使用可能な演算子は+-*/のいずれかです") print(e)
実装
それでは早速ですが、プログラミング作業に入ります。
ソースコードを保管する場所はどこでも構いませんが、本稿ではMacで環境構築にて作成したディレクトリ(src/HelloPython)に保存していきます。またその名称もhello_python.pyとします。
ステップ1
まずは、起動時にファイル名を指定しますが、その部分のプログラミングで、得られた引数を表示する以外取り立てて何もしないプログラムからです。
hello_python01.py
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': argv = sys.argv print(argv)
このように各ステップの冒頭に、そのステップが終わったタイミングでのソースコードを示します。つまりはひとまずのゴールです。ご注意頂きたいのは、このひとまずのゴールは必ずしも最初に示した「完成形のソースコード」の一部分であるとは限りません。説明のために寄り道することもありますので、その点は十分ご注意ください。
寄り道を含め、見間違えていると思った通りの結果が得られず、時間を浪費してしまうことがありますので注意深く参照するか、まず1回目はこの通りにやってみることをおすすめします。
説明
それではこのステップで実装した内容を説明していきます。
#!/usr/bin/env python3.8
この1行目のおまじないはshebang(シェバン)といいます。このトピックに関してはおまじないで説明していますので、ご一読の上こちらへお戻りください。正しく理解しお戻りの方は、本稿で後に実行する際にはpythonコマンドの引数にこのソースコードを指定して実行するので、何の役にも立っていないことに気づかれるでしょう。
import sys
次に3行目です。import文と呼ばれる文です。この文があるおかげでsysの中で書かれている機能が呼び出せます。こちらも詳しくはimport文をご覧ください。
if name == 'main':
shebangのところですでに学習済みかも知れませんが、このトピックに関してはおまじないで説明していますので、ご一読の上こちらへお戻りください。
いまこの段階ではこのif文には全く意味がありません。ですが単独で動作させたり他から呼び出されたりと、いくつかの利用パターンに対応すべくお行儀良くコーディングするならこうなります。ここで初めてこのif文に出会った方であれば、こういったことが必要なケースもあるんだなという認識さえあれば当面は問題無いと思います。
argv = sys.argv
早速import文で使用可能にしたsysの機能を呼び出してargvを取得しています。これは実行時に指定する引数を扱うためのものです。より具体的にはargvは配列(リスト)になっていて、コマンドライン全体をブランク(空白)で区切ったものが要素として格納されています。本稿に記載の順に学習している方は、配列(リスト)ってなんだ?という状態のはずですので、型 リストを参照してください。
print(argv)
最後はprint関数です。この行のおかげで何が起こりそうかという点については説明は不要だと思います。ご想像の通り画面にargvの内容が出力されます。
実行
$ python hello_python01.py data.txt ['hello_python01.py', 'data.txt'] $
引数にdata.txtを指定してみました。リストの1つめは自分自身が、2つめに第1引数が格納されていることが分かります。
最終形のソースコードでは、インプットとなるファイル名を知るために使用しているのです。
ステップ2
引数としてファイル名を1つ取ることを前提にし、中身を読み込んで表示するところまでを実装します。
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': argv = sys.argv # print(argv) if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close print(lines)
説明
# print(argv) if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit()
まず、argvを出力していたprint()はコメントアウトします。
len()関数はリストのサイズ(要素の数)を返す関数です。さきほどargvには1つめに自分自身が、2つめに第1引数が格納されていました。そのため2でなければエラーという扱いで終了するようにしています。if文については、制御構文を参照してください。
print()関数の引数は’シングルクォート’で囲われた文字列に対してformat()メソッドで変更を行っています。何をしているかというと{0}と書かれた部分をargv[0]に書き換えています。
試しに引数ナシで実行してみます。
実行
$ python hello_python02.py usage : hello_python02.py$
{0}の部分がhello_python02.pyに置き換えられてメッセージが出力されています。
続いてファイルの内容を読み取っている部分です。
f = open(argv[1], 'r') lines = f.readlines() f.close print(lines)
ファイルを読み込みモードで開いて、readlines()メソッドで読み込んでいます。これでファイルの中身が一括して読み込まれています。読み込み終わったらファイルに用はないのでcloseしています。最後にprint()で内容を出力しています。
実行
$ python hello_python02.py data.txt ['1 + 1\n', '3 - 2\n', '2 * 4\n', 'a / 2\n', '4 @ 2\n', '2.1 + 3\n', '3 / 0\n', '1 + 2\n'] $
この結果から、各行が要素となってリストを形成していることが分かります。
ステップ3
つづいては、読み込んだ内容を処理します。
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': argv = sys.argv # print(argv) if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close() # print(lines) for line in lines: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2])
説明
# print(lines) for line in lines: data = line.split(" ") print("input: " + str(data))
まずはprint()をコメントアウトします。先ほどlinesはリストになっていることが分かりました。型 リストでも説明していますが、for-inを使うことで1つずつ(1行ずつにあたる)取り出して、処理することができます。
ここではブランク区切りで区切って新たなリストとし、それをprint()しています。
x = int(data[0]) operator = data[1] y = int(data[2])
期待される中身は、数字 演算子 数字とブランク区切りで順に並んだ状態なので、それぞれx operator yに格納すべく処理しています。その際、xとyは整数であるという前提なのでint()を使って型を変換しています。
実行
$ python hello_python03.py data.txt input: ['1', '+', '1\n'] input: ['3', '-', '2\n'] input: ['2', '*', '4\n'] input: ['a', '/', '2\n'] Traceback (most recent call last): File "hello_python03.py", line 21, inx = int(data[0]) ValueError: invalid literal for int() with base 10: 'a' $
どうやらエラーになったようです。よく見ると3行目までは正常に動作し、4行目に数値ではなくて「a」と入っていたために整数への変換時にエラーとなったようです。
このとき、エラーの種類がValueErrorとなっていることに注意してください。このエラーへの対応は、次のステップで行います。
ステップ4
続いてエラーハンドリングを加えていきます。人が用意するファイルですので、間違いはつきものです。そんなとき、たった1行の間違いでその他の行の処理がが止まってしまうととても不幸です。ですので、エラー行だけ処理を行わず、次の行へと処理が移るようにしてみたいと思います。
hello_python04.py
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': argv = sys.argv # print(argv) if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close() # print(lines) for line in lines: try: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2]) except ValueError as e: print(e)
説明
try: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2]) except ValueError as e: print(e)
本来はここまで広範囲に囲う必要はありませんが、繰り返し部分でのエラーは全てスキップして次の処理へ回るようにfor文の処理全体をtry-exceptで囲っています。try-exceptについては制御構文にて説明していますのでご参照ください。
捕捉しているのは、前のステップで確認したValueErrorです。
実行
(LearningPython) MacBookPro:HelloPython $ python hello_python04.py data.txt input: ['1', '+', '1\n'] input: ['3', '-', '2\n'] input: ['2', '*', '4\n'] input: ['a', '/', '2\n'] invalid literal for int() with base 10: 'a' input: ['4', '@', '2\n'] input: ['2.1', '+', '3\n'] invalid literal for int() with base 10: '2.1' input: ['3', '/', '0\n'] input: ['1', '+', '2\n'] (LearningPython) MacBookPro:HelloPython $
どうやら無事に?エラー行での処理ではエラーを表示して、そこで止まることなく先に処理は進んでいるようです。このように適切にエラーを捕捉することで問題を把握するだけでなく、可能な限り処理を続けるという設計も可能となります。
ステップ5
それではいよいよ演算をしてみます。
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': argv = sys.argv # print(argv) if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close() # print(lines) for line in lines: try: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2]) if operator == "+": result = x + y print("result: " + str(result)) elif operator == "-": result = x - y print("result: " + str(result)) elif operator == "*": result = x * y print("result: " + str(result)) elif operator == "/": result = x / y print("result: " + str(result)) else: raise Exception("operator is not valid") except ValueError as e: print("[Error2]使用可能な値は整数です") print(e) except ZeroDivisionError as e: print("[Error2]ゼロで割ることは出来ません") print(e) except Exception as e: print("[Error2]使用可能な演算子は+-*/のいずれかです") print(e)
説明
if operator == "+": result = x + y print("result: " + str(result))
処理は至って簡単です。演算子が+であったら足し算をしてprint()しています。
except ZeroDivisionError as e: print("[Error2]ゼロで割ることは出来ません") print(e)
割り算では0で割ることが出来ないので、もし0で割る操作が指示されるとこのエラーが発生します。そのためZeroDivisionErrorを捕捉しています。
raise Exception("operator is not valid")
そして、演算子が+-*/のいずれでもなかった場合に、エラーをraiseしています。今回はそれをすぐ外側のtry-exceptで捕捉しています。
実行
$ python hello_python05.py data.txt input: ['1', '+', '1\n'] result: 2 input: ['3', '-', '2\n'] result: 1 input: ['2', '*', '4\n'] result: 8 input: ['a', '/', '2\n'] [Error2]使用可能な値は整数です invalid literal for int() with base 10: 'a' input: ['4', '@', '2\n'] [Error2]使用可能な演算子は+-*/のいずれかです operator is not valid input: ['2.1', '+', '3\n'] [Error2]使用可能な値は整数です invalid literal for int() with base 10: '2.1' input: ['3', '/', '0\n'] [Error2]ゼロで割ることは出来ません division by zero input: ['1', '+', '2\n'] result: 3 $
想定通りに動作しましたでしょうか。
ステップ6
最後に、存在しないファイルを指定されときの動作を実装します。
hello_python.py
#!/usr/bin/env python3.8 import sys if __name__ == '__main__': try: argv = sys.argv if len(argv) != 2: print('usage : {0} <filename>'.format(argv[0])) quit() f = open(argv[1], 'r') lines = f.readlines() f.close except FileNotFoundError as e: print("[Error1]指定されたファイルが存在しません") print(e) quit() except Exception as e: print("[Error1]その他のエラーが発生しました") print(e) quit() for line in lines: try: data = line.split(" ") print("input: " + str(data)) x = int(data[0]) operator = data[1] y = int(data[2]) if operator == "+": result = x + y print("result: " + str(result)) elif operator == "-": result = x - y print("result: " + str(result)) elif operator == "*": result = x * y print("result: " + str(result)) elif operator == "/": result = x / y print("result: " + str(result)) else: raise Exception("operator is not valid") except ValueError as e: print("[Error2]使用可能な値は整数です") print(e) except ZeroDivisionError as e: print("[Error2]ゼロで割ることは出来ません") print(e) except Exception as e: print("[Error2]使用可能な演算子は+-*/のいずれかです") print(e)
説明
except FileNotFoundError as e: print("[Error1]指定されたファイルが存在しません") print(e) quit()
ファイルが無いときはFileNotFoundErrorとなるのでこれを捕捉しています。あとはすでに学んだことしかありませんね。
実行
$ python hello_python.py data.txt input: ['1', '+', '1\n'] result: 2 input: ['3', '-', '2\n'] result: 1 input: ['2', '*', '4\n'] result: 8 input: ['a', '/', '2\n'] [Error2]使用可能な値は整数です invalid literal for int() with base 10: 'a' input: ['4', '@', '2\n'] [Error2]使用可能な演算子は+-*/のいずれかです operator is not valid input: ['2.1', '+', '3\n'] [Error2]使用可能な値は整数です invalid literal for int() with base 10: '2.1' input: ['3', '/', '0\n'] [Error2]ゼロで割ることは出来ません division by zero input: ['1', '+', '2\n'] result: 3 $
一つ前のステップと結果自体は変わっていないはずです。
まとめ
いかがでしたでしょうか。このプログラムがやれることは整数の四則演算に過ぎませんが、そこそこの要素を盛り込んだものになっています。
次は、思いっきり具体的な用途、Excelファイルの中身を読み込んで処理するプログラムHello Excelです。ぜひこちらもご一読ください。