Pythonに限らず、多くのプログラミング言語には「関数(Function)」という概念があります。関数は特定の機能を「呼び出す」ために使われます。たとえば今までの回で利用していたprint()も関数のひとつで、()の中に入れた変数や定数を出力するという処理を呼び出します。今回はこの関数と、コードを整理するための「モジュール」について扱います。

関数でしか実現できないこと

プログラムは必ず、キーボード入力やファイルの読み込みといった「外からの情報の入力」と、ディスプレイやスピーカー出力といった「外への情報の出力」を必要としています。入出力のないプログラムも作れますが、動かしても「プログラムの外の世界」に何も影響がないので、CPUとメモリを無駄に消費するという目的以外には使えません。

変数や条件分岐といった制御はあくまでもデータを処理するための手段でしかなく、プログラムの外とやりとりするためには関数が必須です。先に説明した画面への文字出力を行うprint関数もその一例です。

プログラムが読みやすくなる

「関数を使わなくても書けるコード」も、うまく関数を使うことでより良いコードになります。関数を使うとプログラムが読みやすくなるというメリットもあります。

例をあげて説明してみます。絶対値を得ようと思った場合、以下のようにif文で条件分岐させることで実現が可能です。

x = -5
if(x<0):
    x = x * -1
print(x)

2、3行目を見てもらうとわかりますが、「もしxが0より小さければ、xに-1をかける」という処理をしており、絶対値を得ているということが読み取れますね。同じ処理を、絶対値を得る関数abs()を使って書くと、以下のようになります。

x = abs(-5)
print(x)

前者と後者は同じことを実現していますが、どちらのほうがわかりやすいと思いますか。圧倒的に後者ですよね。

関数も、突き詰めると中は処理の塊です。ただ、関数の名前はその「処理の要約」なので、どういう実装で処理が実現されているのかは関数の利用者に隠蔽されており、たとえばabs()では、「どうやって絶対値を求めるか」を理解していなくても、絶対値をパッと得ることができます。人間の思考能力には限界があるので、ゴチャゴチャとした実装を見せられるよりは、関数名という要約を見せられたほうが、何をやっているか判断しやすくなります。

同じコードを何度も書かなくてすむ

プログラムを書いていると、同じ処理を何度も使うという場面が多々あります。たとえば「2つの数字の絶対値を比較する」というプログラムを作る場合、関数を使わないと以下のように絶対値を得るコードが2回出現する冗長なものとなります。

x = 5
y = -10

if(x<0):
    x = x * -1
if(y<0):
    y = y * -1

print('abs x > abs y ?')
print(x > y)

xとyの絶対値を得る処理はほとんど同じなのにもかかわらず、2回書いていますね。2回程度なら書いてもいいような気もしますが、これが5回、10回となればどんどん面倒になっていきます。

先のコードを、関数を使って書き直すと以下のようになります。

x = 5
y = -10

x = abs(x)
y = abs(y)

print('abs x > abs y ?')
print(x > y)

冗長な要素が省かれてずいぶんと読みやすくなったのではないでしょうか。今回はabsのような簡単な関数でしたが、これがたとえば100行以上必要なアルゴリズムだったら、関数で冗長性を減らすことによる多大な恩恵を得られます。

また、同じコードを何度も書いていて、そのなかに「バグ」があることがわかった場合、すべての場所に修正を施す必要があります。一方、関数で同じ処理をまとめていると、理想的には一箇所のみの修正となります。これはプログラムの「保守性(メンテナンス性)」を向上させるというメリットがあります。

関数の宣言と使い方

関数のメリットがわかったところで、関数をどのように作って利用するかを具体的に説明していきます。以下の図を見てください。

「関数」の概念

これが関数の基本的な概念です。関数は入力を受け取り、それを加工して出力する。基本的にはこれだけです。入力と出力はそれぞれなくてもかまわず、入力がない場合は関数の宣言の引数(入力の宣言)をなくし、出力が不要な場合はreturn文(出力の宣言)をなくします。

具体的には以下のように関数を定義します。

# 引数がない関数
def my_func1():
  return 0

# 返り値がない関数
def my_func2(x):
  x = x * -1

関数をどう呼び出すかについては今までさんざん利用したのでなんとなくわかると思いますが、宣言した引数に対応する箇所に入力値を入れることで呼び出します。ひとつ目の変数は引数がないので、呼び出し時に()に何も入れていないものの、後者は引数をとるので()に値を与えています。

print(my_func1())  # 0 と表示される
print(my_func2(5)) # None と表示される

簡単ですね。

ほかに知っておくべきこととしては、引数は複数指定できますが、return文は一度しか実行されないというルールがあることです。これも具体例を示しましょう。

def my_func3(x, y):
    print('A')
    if(x > y):
        return x
    print('B')
    return y

print(my_func3(5,1))
# A
# 5

print(my_func3(2,4))
# A
# B
# 4

上記関数では入力値を2つとっています。コンマで区切られた引数の数のぶんだけ入力を受け付けるという簡単なルールです。そして、内部では2つのreturn文が確認できます。注目して欲しいのはx > yの条件を満たす場合はprint('B')が実行されていないということです。return文はいくつあっても構わないのですが、returnされたあとの関数の処理は一切無視されます。

ほかには「デフォルト引数」や「可変長引数」といった関数の書き方もあるのですが、ここでは紹介を控えます。演習で問題を出すので、余力がある場合は自分で調べて書き方を覚えてください。

global宣言

関数内の処理の実装は、関数の定義の中で完結すべきです。その関数を実行することで、その関数の外の変数などの値を変更すべきではありません。この思想は守るべきですが、時と場合により守らないほうが、良いコードが書ける場合もあります。関数内でのglobal宣言もそのひとつです。さっそくですが次のコードを見てください。

x = 5

def add():
  x += 1

print(x)
add()
print(x)

このプログラムを実行するとどのようになると思いますか。addという関数がxに1をインクリメントするので、

5
6

と出力してくれそうなところですが、実際はエラーが出てしまいます。

Traceback (most recent call last):
  File "/Users/yuichi/Desktop/hello.py", line 7, in <module>
    add()
  File "/Users/yuichi/Desktop/hello.py", line 4, in add
    x += 1
UnboundLocalError: local variable 'x' referenced before assignment

上記エラーを見ると、関数の中で「変数xに値が与えられる前に参照した」というような内容となっています。結論から言いますと、プログラム1行目のxと関数内のxは別物です。そのため関数内のxを使おうとしたところ、エラーが出てしまったのです。どうしても1行目の変数xを関数内で使いたい場合は、関数を以下のように書き換えます。

x = 5

def add():
  global x
  x += 1

こうすることで、関数の外で定義された変数xを関数の中で利用することができるようになります。難しい話となってしまうのですが、関数などを動かすことで「期待される範囲を超えた外の世界に影響がおよぶ」ことを「副作用」といいます。この副作用を減らすことがきれいなコードを書くコツですので、意識してみるといいかもしれません。たとえば上記プログラムだったら、globalを使うより

x = 5

def add(x):
  x += 1
  return x

print(x)
x = add(x)
print(x)

と書くほうがいいです。「同じ入力値を入れたら、同じ出力値を返す」というのが一般的には理想的な関数だと思ってもらえればOKです。

モジュール

Pythonのプログラムは書けば書くほど大きくなります。数百行のコードでしたらひとつのファイルにすべて書いてしまえますが、何千行にもなってくるとコードを複数のファイルに分けたほうが管理がしやすいです。

これは日常生活の整理整頓とまったく同じです。たとえば洋服ダンスがあるとすると、それを使いやすく使うためには下着、シャツ、ズボンといった種類ごとに引き出しを分けて使いますよね。ひとつの大きなダンボール箱にすべての服をつっこんでしまうとどこに何があるかわからなくなり、なおかつ服もきれいに管理できずにシワシワになってしまいます。

プログラムのファイルを分けないと、後者のような乱雑な服の管理法に近い形でコードを書くことになります。ひとつの大きなファイルのなかにさまざまなコードをゴチャゴチャと書くのでどこで何をやっているのかわからなくなってきます。

一方、特定の処理ごとにファイルを分けて「このファイルはXXの処理」「このファイルはYYの処理」などと整理すると、XXの処理を追加したり修正したりする際にすぐに場所がわかります。

Pythonでは「ファイルに分けられた各プログラム」のことをモジュールと呼んでいます。ここではこのモジュールを使ったり、作ったりする方法について学びます。

モジュールを利用するとコードが整理できる

モジュールの利用

モジュールを自分で作ることも可能ですが、まずはPythonが提供してくれているモジュールを利用することからはじめていきましょう。

モジュールを利用するには「import宣言」が必要です。たとえば、数学処理がまとめられたmathモジュールを利用するには以下のようにします。

# import モジュール名
import math

このようにimportをすると、mathモジュールに入っている関数などが利用できるようになります。たとえばmathモジュールの切り捨て関数を使うには以下のようにします。

>>> import math
>>> math.floor(5.5)
5.0

モジュール内の関数を呼び出すには"モジュール名.関数()"としますが、毎回毎回これを書くのが面倒であれば、適当な名前の変数に代入してしまってもかまいません。

>>> import math
>>> floor = math.floor
>>> floor(5.5)
5.0

fromを使うことで、モジュール内の関数をモジュール名なしで呼び出すことも可能になります。

# from モジュール名 import 関数名
>>> from math import floor
>>> floor(5.5)
5.0

モジュール内の関数すべてをモジュール名なしで呼び出すには以下のようにワイルドカードを使います。ただ、このような乱雑なモジュールの利用法はコードの安全性を保つためにも推奨できません。

>>> from math import *

今回はすでにpythonから提供されているモジュールを読み込みましたが、自分で作成したモジュールもまったく同じ方法で読み込めます。

モジュールの作成

モジュールの作成は簡単です。本連載の最初に説明したように.pyという拡張子をつけたファイルにpythonのコードを書くだけです。ここではモジュールmy_util.pyを作成し、それをmain.pyから呼び出す例を示します。

  • my_util.py
def say_hello():
    print('hello!')

def say_python():
    print('python!')
  • main.py
import my_util

my_util.say_hello()
my_util.say_python()
  • 実行結果
hello!
python!

特別に難しいことはありませんね。モジュールを書くにあたって注意すべきすることは、それが

  • 再利用可能か
  • 似た処理のみをまとめているか

というあたりです。

たとえば標準ライブラリで提供されていない特殊な数値計算が必要なら、その計算のためのモジュールを作ってもよいでしょう。ただ、そこに特殊な文字列処理であったり、ネットワークの処理も書くというのは誤った設計です(もちろん時と場合によりますが)。

また、そのモジュールを誰しもが簡単に使えるようにすることが理想です。実際は複雑なプログラムを分割するためだけにモジュール化することも多いのですが、それでも「使いやすい」ように書くことを心がけておくといいかもしれませんね。

自作モジュールのテスト

余談となりますが、モジュールのテストについて記載します。複数のファイルを使ったプログラムを起動するとき、それらのファイルは大きく分けて

  • 起動する起点となるファイル (python xxx.py で指定されるファイル)
  • 起動されたファイルがimportするモジュールのファイル

に分かれます。すべてのファイルは前者にも後者にもなりえるのですが、前者の場合のみだけ実行したい特別な処理がある場合は

if(__name__ == '__main__'):
  処理

とすると、上記の「処理」はモジュールとして読み込んだ場合は実行されません。たとえば先程のmy_util.pyを以下のように変更します。

def say_hello():
    print('hello!')

def say_python():
    print('python!')

if(__name__ == '__main__'):
    print('my_util.py: loaded as script file')
else:
    print('my_util.py: loaded as module file')    

そしてこれを、直接呼び出してみます。

% python my_util.py
my_util.py: loaded as script file

if文の条件がTrueとなり、Trueの際の処理が実行されていますね。次に、先ほど作ったmain.py経由でmy_util.pyを使ってみます。

% python main.py   
my_util.py: loaded as module file
hello!
python!

今度はif文のelse節が実行されていますね。通常、直接起動されないモジュールを作る際、そのif(__name__ == '__main__')の中にモジュールをテストするような処理を書いておくと、プログラムの保守性が増します。覚えておくと便利かもしれません。


演習1

絶対値を返す関数my_absを作成して下さい。この関数内では標準ライブラリなどで提供されているabs関数などは使わないでください。

演習2

引数で与えた数だけフィボナッチ数を配列で返す関数my_fiboを作成してください。フィボナッチ数については調べればすぐにわかるはずです。

演習3

演習1、2で作成した関数もしくは自分で適当に作成した関数をモジュール化し、それを別ファイルから利用してみてください。import文、from文の両方を使ってみてください。

※解答はこちらをご覧ください。


Pythonのプログラムを起動する際にオプションとしていろいろなパラメータを与える「コマンドライン引数」と呼ばれる手法があります。次回はコマンドライン引数の扱い方について学びます。また、プログラムに対してキーボードで入力を行う標準入力についても学びます。

執筆者紹介

伊藤裕一(ITO Yuichi)

シスコシステムズでの業務と大学での研究活動でコンピュータネットワークに6年関わる。専門はL2/L3 Switching とデータセンター関連技術およびSDN。TACとしてシスコ顧客のテクニカルサポート業務に従事。社内向けのソフトウェア関連のトレーニングおよびデータセンタとSDN関係の外部講演なども行う。

もともと仮想ネットワーク関連技術の研究開発に従事していたこともあり、ネットワークだけでなくプログラミングやLinux関連技術にも精通。Cisco社内外向けのトラブルシューティングツールの開発や、趣味で音声合成処理のアプリケーションやサービスを開発。

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009年度 IPA 未踏プロジェクト採択

詳細(英語)はこちら