2.4 数学関数とユーザー定義関数、入力バリデーション

(1/1)
ブルースクリーンのイラスト
Pythonを使って、平方根、べき乗、三角関数という算術関数の計算を行うとともに、前々回に作った1から10までの整数の和を求めるプログラムをユーザー定義関数にする方法や、前回の宿題だった入力バリデーションを学ぶ。
(2024年7月20日)makePassword.py -- docstringをGoogleスタイルに変更.
(2024年6月15日)標題、関数のコメントを複数行形式に変更.「コラム:RASISとは」追加.

目次

サンプル・プログラム

平方根の計算

平方根の計算 - Python
"sqrt.py" は、3の平方根 \( \sqrt{3} \) を計算し、結果を画面に表示するプログラムだ。
import math			# 数学関数モジュール
x = 3
y = math.sqrt(x)	# 平方根を求める関数
print(y)
math.sqrtが平方根を求める関数で、Python に組み込まれている。ただし、こうした算術関数を使うには、あらかじめ mathモジュールを導入する必要があり、冒頭の importを使って導入する。
使い方は print関数input関数と同じで、引数の平方根を戻す。つまり、\( \displaystyle \sqrt{x} \) の計算結果を戻す。

べき乗の計算

べき乗の計算 - Python
引数が2つ以上ある算術関数もある。
"pow.py" は、べき乗 \( 2 ^ 3 \) を計算し、結果を画面に表示するプログラムだ。
import math			# 数学関数モジュール
x = 2
y = 3
z = math.pow(x, y)	# べき乗を求める関数
print(z)
math.powがべき乗を求める関数で、Python に組み込まれている。あらかじめ mathモジュールを導入する必要があるのは、前述の sqrt関数と同じ。
使い方は sqrt関数と同じで、\( \displaystyle x ^ y \) の計算結果を戻す。

三角関数の計算

三角関数の計算 - Python
三角関数や逆三角関数も用意されている。"sin.py" は、正弦 \( sin(45) \) を計算し、結果を画面に表示するプログラムだ。
import math						# 数学関数モジュール
x = 45
y = math.sin(math.radians(x))	# 度をラジアンに変換して正弦を求める
print(y)
三角関数の引数の単位はラジアンなので、このプログラムのように度を引数にする場合は、まず math.radians関数を使って度をラジアンに変換し、その戻り値を math.sin関数に渡す。

ユーザー定義関数

Python は他のプログラミング言語と同様、ユーザーが関数を定義(プログラミング)することができる。
"userFunc1.py" は、前回つくった開始値から終了値までの整数列の合計を求める計算を、ユーザー定義関数 sumNumbers にしたものである。
import atexit
import sys

# 文字色のエスケープシーケンス
COLOR_CYAN   = '\033[36m'	# シアン
COLOR_GREEN  = '\033[32m'	# 緑色
COLOR_YELLOW = '\033[33m'	# 黄色
COLOR_END    = '\033[0m'	# 終了

def sumNumbers(fromInt, toInt):
	"""fromIntからtoIntまでの整数を合計する.
	
	Args:
		fromInt(int): 開始値
		toInt(int):   終了値
	
	Returns:
		int 合計値
	"""
	n = 0
	for i in range(fromInt, toInt + 1):
		n += i
	return n

# メイン・プログラム ========================================================
# キーボードから入力する(文字列).
startStr = input("開始=")
goalStr  = input("終了=")

# 整数に変換する+エラー対策
try:
    startInt = int(startStr)
    goalInt  = int(goalStr)
except Exception as e:
    print("エラー > " + str(e))
    sys.exit()

# 合計を計算する.
sumInt = sumNumbers(startInt, goalInt)

# 表示文字列を生成する.
ユーザー定義関数は def文ではじめ、半角スペースを空けて、関数名を定義する。関数名は変数名と同じルールの名前を付ける。
括弧 (...) の中に引数を並べる。引数は、関数定義の中で変数として扱われる。ここでは、開始値を fromInt、終了値を toInt とした。
関数定義は、インデントを空けて行う。for文 と同じように、インデントが空いている範囲が def sumNumber の定義である。内容としては、前回つくったプログラムと同じだ。
ユーザー定義関数は、return文 のあとに書いた値(変数)を関数の戻り値として返す。
ここで、行頭インデントの数に注意してほしい。
for文 の範囲に行頭インデントを付けることは、すでに学んだとおりだが、def文 の範囲にも行頭インデントが必要だ。したがって、for文 の範囲は、行頭インデントを2つ分に増やさなければならない。通常はタブ2文字にするが、半角スペースを使っている方は、1文字でも多くすればいい。これを図示すると下のようになる。
ユーザー定義関数とインデント - Python
ユーザー定義関数とインデント
また、行頭インデントの位置は合わせること。sumNumbers 関数では、
n = 0
for i in range(fromInt, toInt + 1):
return n
の3行の行頭インデントを合わせること。半角スペースを使っている方は、1文字でもスペースが足りなかったり多かったりすると、エラーになってしまうので注意してほしい。
ユーザー定義関数の呼び出し方は、
sumInt = sumNumbers(startInt, goalInt)
のようにする。引数として渡す変数名は、定義で使った変数名と異なるものでもよい。また、数値そのものを渡すこともできる。

入力バリデーション

入力バリデーション - Python
前回は int関数で発生したエラーを例外処理するプログラムにしたが、今回は、入力値が妥当なものかを検査する入力バリデーションを組み込んでみる。"userFunc2.py" が、そのプログラムである。ソース・プログラムが長いのだが、まず、メイン・プログラムの流れを見ておこう。

まず、input文を使ってキーボードから開始値と終了値を入力し、それぞれ変数 startStrgoalStrに代入する。

次に、開始値の入力バリデーションを行う。
まず、後述するユーザー定義関数 str2intを使って整数に変換し、変数 startIntに代入する。この関数は、内部で変換エラーが起きたときは False(偽)を戻す。そこで、変数 startIntFalseだったら、後述するユーザー関数 dispErrorMessageAndExit を呼び出し、エラー・メッセージを表示し、プログラムを終了する。
続いて、変数 startIntがあらかじめ用意した変数 INPUT_MIN より小さいかどうか、変数 startIntがあらかじめ用意した変数 INPUT_MAX より大きいかどうかを検査し、引っかかったら同様にユーザー関数 dispErrorMessageAndExit を呼び出す。

終了値の入力バリデーションについても、同様の検査を行う。
# メイン・プログラム =======================================================
# キーボードから入力する(文字列).
startStr = input("開始=")
goalStr  = input("終了=")

# 入力バリデーション
startInt = str2int(startStr)
if (startInt == False):
	dispErrorMessageAndExit("開始値 " + startStr + " に整数以外の文字が含まれています")
if (startInt < INPUT_MIN):
	dispErrorMessageAndExit("開始値 " + startStr + " は最小値 " + str(INPUT_MIN) + " より小さい")
if (startInt > INPUT_MAX):\
	dispErrorMessageAndExit("開始値 " + startStr + " は最大値 " + str(INPUT_MAX) + " より大きい")

goalInt  = str2int(goalStr)
if (goalInt == False):
	dispErrorMessageAndExit("終了値 " + goalStr + " に整数以外の文字が含まれています")
if (goalInt < INPUT_MIN):
	dispErrorMessageAndExit("終了値 " + goalStr + " は最小値 " + str(INPUT_MIN) + " より小さい")
if (goalInt > INPUT_MAX):
	dispErrorMessageAndExit("終了値 " + goalStr + " は最大値 " + str(INPUT_MAX) + " より大きい")

if (startInt >= goalInt):
	dispErrorMessageAndExit("開始値 " + startStr + " は終了値 " + goalStr + " と同じか大きい")

# 合計を計算する.
sumInt = sumNumbers(startInt, goalInt)

# 表示文字列を生成する.
dispStr = COLOR_YELLOW + startStr + COLOR_END + 'から' + COLOR_GREEN + goalStr + COLOR_END + 'の和は' + COLOR_CYAN + str(sumInt) + COLOR_END + 'です.'
# 画面に表示する.
print(dispStr)
if文は "if (条件式)" のように書き、条件が成り立つときに、それに続くブロック(インデントの部分)を実行する。
ここでは、入力値に整数以外の文字が含まれているかどうかを調べ、含まれていたら(条件が成立したら)、ユーザー定義関数 dispErrorMessageAndExit を実行する。つまり、エラー・メッセージを表示し、プログラムを終了する。
以下同様に、最小値(INPUT_MIN)より小さいかどうか(最小値を含まない;未満)、最大値(INPUT_MAX)より大きいかどうか(最大値を含まない;超)を順に検査する。

それでは、ユーザー定義関数毎に説明することにしよう。
def str2int(str):
	"""引数を整数に変換する.変換できなければFalseを返す.
	
	Args:
		str(str): 変換したい数字(文字列)
	
	Returns:
		int: 整数 / Bool: True 整数である,False 整数ではない
	"""
	#整数変換してみる
	try:
		int(str)
	#失敗したらFalseを返す.
	except ValueError:
		return False
	#成功したら変換結果を返す
	else:
		return int(str)
ユーザー定義関数 str2int は、引数を整数に変換するものである。組み込み関数の int関数との違いは、前回つくったエラー処理を盛り込んでいるところだ。
まず int関数で整数変換を試みて(try文)、例外が発生したら(excepts節False(偽)を return文を使って戻す。ここで、ValueErrorint関数で発生する変換エラーを意味する。
例外が発生しなかったら=正しく処理が行われたら(else節)、int関数の結果を return文を使って戻す。
ユーザー定義関数では、このように矛盾しない場合分けが行われていれば、複数の return文を使うことができる。
def dispErrorMessageAndExit(errmsg):
	"""エラー・メッセージを表示してプログラムを終了する.
	
	Args:
		errmsg(str): エラー・メッセージ
	
	Returns:
		None
	"""
	print(COLOR_YELLOW + "エラー > " + errmsg + COLOR_END)
	print("プログラムを終了します")
	sys.exit()
ユーザー定義関数 dispErrorMessageAndExit は、エラーメッセージを表示してプログラムを終了する。後述の通り、バリデーションを行う都度、エラーメッセージの表示を行う必要があるので、このようにユーザー定義関数にした。
似たような処理をユーザー定義関数にすることで、プログラム全体を短くすることができ(生産性の向上)、不具合を減らすことができる(品質の向上)。

さて、メイン・プログラムのほとんどが入力バリデーションなのだが、よく見ると、開始値と終了値に対して同じバリデーションを行っている。そこで、この処理をユーザー定義関数にすることで、メイン・プログラムをシンプルなものにしてみたい。

入力バリデーションの改良

"userFunc3.py" のメイン・プログラムで行っている入力バリデーションをユーザー定義関数にしたものが "userFunc3.py" である。
def validateNumber(num, label, min, max):
	"""数字(文字列)を数値に変換する.バリデーション付き.

	変換できなかったときはエラー・メッセージを表示してプログラムを終了する.

	Args:
		num(str):	数字(文字列)
		label(str):	データ名称
		min(int):	最小値
		max(int):	最大値
	
	Returns:
		int: 変換した数値
	
	"""
	# 整数に変換する.
	i = str2int(num)
	#バリデーション
	if (i == False):
		dispErrorMessageAndExit(label + " " + num + " に整数以外の文字が含まれています")
	if (i < min):
		dispErrorMessageAndExit(label + " " + startStr + " は最小値 " + str(min) + " より小さい")
	if (i > max):
		dispErrorMessageAndExit(label + " " + startStr + " は最大値 " + str(max) + " より大きい")

	return i
ユーザー定義関数 validateNumber は、入力値(バリデーション対象)、名前、最小値、最大値を引数とし、入力値が整数かどうか、最小値と最大値の範囲にあるかを検査し、問題がなければ変換した整数値を戻す。
	"""
	# 整数に変換する.
	i = str2int(num)
	#バリデーション
	if (i == False):
		dispErrorMessageAndExit(label + " " + num + " に整数以外の文字が含まれています")
	if (i < min):
		dispErrorMessageAndExit(label + " " + startStr + " は最小値 " + str(min) + " より小さい")
	if (i > max):
		dispErrorMessageAndExit(label + " " + startStr + " は最大値 " + str(max) + " より大きい")

	return i

# メイン・プログラム =======================================================
# キーボードから入力する(文字列).
startStr = input("開始=")
goalStr  = input("終了=")

# 入力バリデーション
このようにメイン・プログラムは短くなり、全体の流れが一目で見通せるようになった。

ここで応用問題――浮動小数を入力バリデーションするにはどうしたらいいだろうか。皆さんが考えてみてほしい。

練習問題

次回予告

次回は、後から読んだときに分かりやすいように、また他人が呼んだときにも内容が伝わるように、コメントを書く習慣をつけようという話をする。

コラム:DRY原則

整理をしている人のイラスト
今回作成したユーザー定義関数 validateNumberのように、繰り返してあらわれる処理を単一なものにすることをDRY原則(Don't Repeat Your Self)と呼ぶ。アンディー・ハントとデイブ・トーマスが1999年(平成11年)に著した『達人プログラマー』に登場するプログラミングのお作法の1つだ。
同じコードを何度も書くことは、その分、設計量が増え、キーボード入力が増え、プログラムの生産性を低下させる。
また、あるコードに不具合が見つかったとき、同じコードがプログラムの中に複数散らばっていると、そのすべてを修正しなければならない。修正したことによって、ある場所では別の不具合が発生するかもしれない。
そこで、DRY原則を適用しようというわけだ。

しかし、なんでも機械的にDRY原則を適用すればいいというものではない。
たまたまコードが同じだったという処理も有り得る。将来的なバージョンアップで、それぞれのコードが別のものに変化していく可能性がある場合、DRY原則を適用してはいけない。この違いは設計段階で明確にしておくといいだろう。
また、非同期処理が含まれるコードは、安易に共通化することはできない。

コラム:RASISとは

レビュアーのイラスト(女性)
コンピュータシステムを評価する指標として――
  1. Reliability(信頼性)‥‥どんな使い方をしても障害を起こさないこと。
  2. Availability(可用性)‥‥高い稼働率を維持できること。
  3. Serviceability(保守性)‥‥障害が起きた場合に迅速に復旧できること。
  4. Integrity(保全性)‥‥データが矛盾を起こさずに一貫性を保っていること。
  5. Security(安全性)‥‥機密性が高く、不正アクセスがなされにくいこと。
――の5つを挙げることが多い。この5つの頭文字をとって RASIS (レイシス) と呼ぶ。

システム開発では、これら5つを非機能要件――顧客から提示される要求要件には毎時されていないものの、実装すべき要件――として、要件定義書に記載する。

今回学んだ入力バリデーションは、Reliability(信頼性)にあたる。つまり、利用者がどんな想定外の入力をしても、それによってシステムが停止したりダウンしないようにするのが Reliability(信頼性)という非機能要件の1つだ。
入力バリデーションは、フールプルーフにもとづいて行う。フールプルーフとは、誤った操作を行っても重大な事故を招かないよう設計すること。プログラムでいう事故とは、システム停止やダウンを指す。
フールプルーフの原語は "Fool Proof"――Foolは「愚かな、愚か者」。Proofは「耐える」です。つまり、「愚か者の使用に耐える」と言い換えた方が分かりやすいかもしれない。

利用者を「愚か」と考えるのは技術者の驕りだが、利用社が想定外の使い方をすることがあるのは紛れもない事実である。あなたがパソコン初心者だった頃を思い出してほしい。粗酒とウェア・マニュアルが分厚すぎて読む気が失せ、とりあえず動かしはじめてみたことがあるだろう。そんな初心者が、どんな入力をするか分かったものではない。それでもシステム不具合を起こさないよう、逐一、入力バリデーションを行い、利用者に使用法に誤りがあることを伝えるのがフールプルーフである。
(この項おわり)
header