step09 関数とモジュール
これまで扱ってきたのは短いサンプルプログラムばかりである。コマンドラインから引数を渡せるようになり、少しは実用的にはなったものの、やはり一気に書け、一目で理解できる程度の簡単なものがほとんどだった。実行の流れも、条件分岐や繰り返しこそあれ、基本的には1行目から最終行への単純な進行だった。
この「極・楽 python 講座 【基礎編】」もそろそろ終盤に差し掛かり、「極・楽 python 講座 【応用編】」の門を叩く日も近づいてきた。そこではより複雑な、より長いプログラムを扱うことになるだろう。また、そもそも皆さんがプログラミングを学んでいるのは、自分のやりたいことをプログラムで表現し、自在にコンピュータを操りたいと思えばこそなのだ。
なにやら愚痴っぽい書き出しになったが、何を言いたいのかといえば、長いプログラムには、短いプログラムでは必要がなかった、全体をどのように組み立てるかという構成論が必要になるということである。pythonを始め、主要なプログラミング言語はどれも、大規模なプログラムを整然と、わかりやすく構成していくためのフレームワークが備わっている。時代とともにプログラムの規模と複雑さは指数関数的に増大しており、それにつれて新しい構成論のパラダイムも過去数十年にわたって提唱されてきた。
- 構造的プログラミング
- オブジェクト指向プログラミング
- デザインパターン
- 論理プログラミング
- 関数型プログラミング
- エージェント指向プログラミング
- 深層学習による問題解決
- 量子コンピューティング
この講座(【基礎編】【応用編】)では、このstepでは、最新鋭のパラダイムにこそ触れないが、最初の2つは守備範囲である。
このstepでは、構造的プログラミングの構成要素である関数とモジュールについて学ぶ。これにより、より長く複雑なプログラムを設計したり、読解したりする【応用編】への心構えができるだろう。
また、【応用編】のstep03ではまた、オブジェクト指向プログラミングの構成要素であるクラスとオブジェクトを学ぶ。論理的思考を促し、GUIプログラミングにも欠かせない技術だからである。
関数の定義と呼び出し
「関数」と聞くと、高校数学の授業を思い出して身構える人がいるかもしれないが、pythonを始め、ソフトウェアでいう関数はそんなに堅苦しいものではないから安心してよい※1。「一定の計算をして答を返す、計算パッケージ」をイメージすればいい。
1 ただし、「関数型プログラミング言語」とか、pythonでも「ラムダ関数」などは数学的な関数に近い。
実は、私たちはpythonの関数をもう使っている。step5 コマンドライン引数で習ったheron.pyでは、数値を整数化する関数int( )や平方根を求める関数sqrt( )を使った。
そもそも最初のstep1 プログラミングとはでも、ユーザから入力文字列を得るために関数input()を使い、プログラムの応答を出力するために関数print()を使っている※2。
この場合、それらの計算の詳細に私たちは興味がなく、データを渡し、結果をもらうだけで十分だ。その方がプログラムもはるかに読みやすくなる。つまり、関数は、繰り返し利用する計算アルゴリズムの一部をパッケージ化してくれるのである。
2 名前の後の( )は何だろう、と思った人も多いだろう。これは、関数にデータを渡すインタフェースだ。また、関数を変数に代入しているのは、関数が返したデータを受け取っているのである。これらをそれぞれ、関数の引数、値(返り値)という。
int( )やinput( )やprint( )の動作がプログラム中に書かれていないのは、python側であらかじめ定義されているシステム関数だからである。pythonにはシステム関数が多数あり、言語本体やライブラリ・モジュールで定義されている。どのモジュールにどんな関数があるかという詳細は言語リファレンスを参照のこと。すでに用意されている関数があるのに、自分で書くのは思考と時間の無駄なので、言語リファレンスには普段から目を通しておくとよい。
本stepではシステム関数ではなく、自分で書いたプログラムの部分をユーザ関数として定義する方法を学ぶ。平方根sqrt( )ほどではなくても、自分で書いたプログラム中にも、別の機会に再利用したくなる部分、つまり汎用性のある部分は必ず存在する。
そうした部分に
- 名前をつけ、
- データを受け渡す出入口を設け、
- 他のプログラムから呼び出せる手段を作れば、
ここではstep05で学んだheron.pyを題材にしよう。これはヘロンの公式を用いて、3辺の長さから3角形の面積を求めるプログラムだった。リスト9-1に再掲する。
サンプルプログラムを見慣れていると、どこに改良の余地があるのか見えにくいかもしれないが、注意深く観察すると、
- 「ヘロンの公式」そのものが、分かりやすい形でユニット化されていない。公式と、それを使って面積を求める部分(つまり、公式をテストする部分)も混在している。
- 2種類のエラー処理(コマンドライン引数のチェックとヘロンの公式の適用可能性のチェックが区別されていない。
- 「ヘロンの公式」を計算するという機能を、他のプログラムから呼び出すインタフェースが存在しない。
これらを解決するには、
- 「ヘロンの公式」のプログラムを独立させ、名前(関数名)をつける。
- 関数に渡す引数のインタフェースを定義する。
- 関数が返す値のインタフェースを定義する。
関数heron(a, b, c)を定義することで、全体の見通しがはるかによくなり、前述の問題点の1と2が解消されたことに気づくだろう。def構文はややこしいと感じるかもしれないが、これでも変数の型を宣言しなくてはならない大多数の言語(C系、javaなど)に比べるとかなり簡素だ。
関数heronは、
- 引数として3辺の長さを受け取る
- 値として面積を返す
- 適用不可能な引数(3角形の3辺になりえないデータ)を受け取ったときは、エラーを示すために-1を返す
関数は、構造化プログラミングのパラダイムにおいて、プログラムを組み立てる基本的なブロックである。関数(function)とは、このブロックが入力値を出力値に変換する機能を持つところからくる名前だが、言語によっては同じものを、プログラムの処理手順(routine)の一部を切り出したものという意味でサブルーチン(subroutine)と呼ぶ。
変数名については注意が必要だ。main( )関数で使われている変数a, b, c, Tと、heron( )中のa, b, c, Tとは別の変数である。これらはどちらも、その関数の中だけで有効なローカル変数であり、これに対して、モジュール全体で有効な変数をグローバル変数という。
※3
3 これはかなり端折った説明で、正確を期すにには関数名なども含むすべての名前とその有効範囲(scope)について議論しなくてはならない。詳しくは教科書の「4.10 名前空間とスコープ」(128ページ)を参照のこと。
モジュールの定義と利用
リスト9-2のプログラムをもう少し見てみよう。
関数heron(a, b, c)の他に、関数main( )が定義されている。また、ラスト2行にはオマジナイめいた記述
if __name__ == '__main__':
main()
もある。これらは一体何か?実はこれらの仕組みが、前述の問題点の3つめ、つまり他のプログラムからの再利用に関わっている。言い換えれば、これらの記述によって、このプログラムheron3.pyはモジュールとして定義されているのである。
実は私たちは、これまでのstepでもモジュールを利用している。
import sys
import re
from math import sqrt
などがそれだ。最初の2つはsysまたはreモジュール全体を利用する宣言、最後の1つは、mathモジュールの中のsqrt( )関数だけを利用する宣言である。ただし、これらはあらかじめ用意されているシステムモジュールであるから、私たちは中身を気にする必要がなく、使い方さえ分かればよい。これに対してリスト9-2では、自前のheron3モジュールを定義している。ラスト2行のオマジナイは、このモジュールがコマンドラインから直接呼び出された場合は、関数main( )を呼びだすという指示である。※4
4 C系の言語などと違って、pythonではmainという名前を特別扱いしない(俺々とかでもよい)。また、常に他のプログラムにimportされるモジュールでは、main( )関数は省いても構わない。
heron3.pyが直接呼び出されない場合、つまり他のプログラム中でモジュールとしてimportされた場合には、main( )関数は呼び出されない。
リスト9-3に、「ヘロンの公式」モジュールを利用するテストプログラムを示す。testheron.pyとheron3.pyは同じディレクトリになくてはならない※5。
5 環境変数PYTHONPATHなど、いくつかの方法で、他の場所にモジュールを格納しておくことができる。
Δ(読み方は「デルタ」)などの変な変数名を使ってみたのは、エンコードがutf-8だからこそできる「お遊び」である。utf-8では、1バイト文字とか2バイト文字といった区別はないから、変数名や関数名は日本語でもよい。その他、Unicodeに搭載されているどんな国語でもよいのだ※6。6 とはいえ、提出課題にチャガタイ・テュルク語とかを使うのは、やめてほしい。
このテストプログラムでは、heron3.pyをモジュールとしてimportしている。
import heron3
T = heron3.heron(a, b, c)
がインポートの宣言と関数呼び出しである。ヘロンの公式の定義を知らなくても、利用はできる。ここではコマンドライン引数ではなく、リスト(のリスト)に格納されたいくつかの3角形の面積をすべて求める使い方をしている。モジュールにすることで、汎用性が増したのである。heron関数のみを利用するのなら(だって、他に関数ないし…)、宣言と呼び出しは、
from heron3 import heron
T = heron(a, b, c)
とも書ける。このようにモジュールは、関数よりも1段階大きな構造化単位である。関数とモジュールを活用することで、より大規模なプログラムに取り組むことができ、自分が一度作成したプログラムを資産として再利用できるのである※5。
5 プログラムの構造化を支援する仕組みとして、モジュール群を組織するためのパッケージがあるが、この『極・楽 python 講座 【基礎編】【応用編】』では省略する。
演習:2次方程式の解
2次方程式の解の公式を関数として定義し、それを含むモジュールquadraticを定義しなさい。リスト9-4ではテストプログラムmain( )を含む形を掲載したが、皆さんはリスト9-3を参考に、他のプログラムファイルtestquadratic.pyを作成して、モジュールとして利用すること。
ヒント:pythonの関数は複数の値を返せる。