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 未螏プロゞェクト採択

詳现(英語)はこちら