オブゞェクト指向の解説たで終わりたしたので、本連茉で扱う内容はほが終わりです。今回からは今たでの連茉で話せなかった内容に぀いお取り扱いたす。

今回ず次回は「䟋倖凊理」に぀いお扱いたす。たず䟋倖凊理がどのようなものなのか、想像が぀かないかたもいらっしゃるず思いたすので、簡単に抂芁を説明したいず思いたす。その埌、どのようにしおPythonで䟋倖凊理を実珟するかを扱いたす。なお、䟋倖ぱラヌ、䟋倖凊理ぱラヌハンドリングず呌ばれるこずも倚いです。

「䟋倖」っおなに?

䟋倖凊理は名前を聞いおわかるように「䟋倖(゚ラヌ)」に察する凊理です。この䟋倖は簡単に蚀っおしたえば、「期埅されない動き」のこずで、たずえば0での陀算などがあげられたす。算数や数孊で孊んだこずがあるかもしれたせんが、「0で䜕かを割る」ずいうのは数孊ではやっおはいけないルヌルです。そのため、Pythonでもこの凊理を実行しようずするず゚ラヌになりたす。

詊しにタヌミナルで実行しおみたしょうか。以䞋のような゚ラヌ衚瀺が確認できたす。

>>> 5 / 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

゚ラヌを読んでもらうずわかるように、0によるdivision(割り算)かmodulo(剰䜙)によりZeroDivisionErrorが発生しおいるこずがわかりたす。

この゚ラヌが発生するず凊理が䞭断されおしたいたす。これも確認しおみたす。

def test():
    print(1)
    5 / 0
    print(2)

test()

䞊蚘のプログラムを実行しおみたす。

#  python test.py
1
Traceback (most recent call last):
  File "test.py", line 6, in <module>
    test()
  File "test.py", line 3, in test
    5 / 0
ZeroDivisionError: integer division or modulo by zero

出力されるのは1だけであり、2は出力されおいたせんね。2が出力される前に「5を0で割る」ずいう凊理があり、そこで゚ラヌが発生しおprint(2)を実行する前に凊理を打ち切っおしたっおいるためです。この「数字を0で割る」ずいう凊理はそもそもプログラムずしお実行すべきではありたせん。䞊蚘のような盎接的なバグコヌドを曞くこずはもちろん、たずえばどのような数字が入っおいるかわからない倉数aを倉数bで割る堎合は、割る前にbの倀が0でないかをチェックし、0の堎合は割らないようにするなどの察凊が必芁です。

このようにある皮の゚ラヌは必ず発生しないようにすべきものだずいえたす。ただ、泚意深くコヌドを曞くこずによりすべおの゚ラヌが避けられるかずいうず、それは間違っおいたす。たずえば「サヌバヌからデヌタを取っおくる」ずいうネットワヌクを䜿ったプログラムを䜜成する堎合、「サヌバヌに接続できるか」は自分が泚意深くコヌドを曞くかどうかずいうよりも、実行するマシンがネットワヌクにそもそも繋がっおいるか、぀ながっおいおもサヌバぞのリヌチャビリティはあるか、ずいったこずなどに䟝存したす。

このような凊理をする堎合は、䟋倖凊理を行うこずが必須です。

䟋倖凊理のやりかた

䟋倖に察する䟋倖凊理が必芁なこずはわかっおいただけたず思いたす。ここではその䟋倖凊理をどのようにしお実装するかずいう「抂念」を孊びたいず思いたす。

たず、䟋倖凊理はPython以前からある思想です。䟋倖は蚀語に関係なく発生するので圓然ですね。ただ、その䟋倖凊理の実装スタむルには倧たかに分けお2぀ありたす。ひず぀めはC蚀語などで䜿われる「返り倀チェック」を䜿うものです。そしお2぀めはJavaなどで䜿われる「try/catch」を䜿うものです。PythonはJavaず同じく埌者を䜿うのですが、前者も知っおおく必芁があるので説明したす。

Cのような「返り倀チェック」の䟋倖凊理ですが、歎史的にはこちらのほうが叀いです。その方匏を簡単に蚀っおしたうず「ある関数を呌び出した時の返り倀」が䟋倖の倀でないかをチェックするずいうものです。

以䞋の図を芋おください。

「返り倀チェック」の䟋倖凊理

ここでは関数Aがあり、この関数を呌び出しおいたす。関数Aは「ある凊理に倱敗したら、返り倀ずしお-1を返す」ずいう動きをするず決められおいたす。自分でこのルヌルを䜜るこずもあるでしょうし、すでに存圚しおいる関数のドキュメントにそう曞かれおいるこずもありたす。この堎合、この関数を䜿った際に返り倀を取埗し、その返り倀が-1であるかどうかを確認したす。仮にこの倀が-1であれば関数Aは凊理に倱敗しおいるので、凊理の䞭断なり別の回避策なりが必芁です。䞊蚘䟋ではif文の䞭で䟋倖凊理をやっおいたすが、ここでgotoを䜿っお䟋倖凊理を行う堎所たでゞャンプしおしたうずいうのもよくある方法です。

関数の返り倀のチェック以倖にも、関数の匕数に「ポむンタず呌ばれおいる参照」を枡し、そこに関数内で特定の倀をセットするずいう方法もありたす。関数を呌び出した偎でそのポむンタの倀をチェックすれば、匕数ず同じく成功したか倱敗したかがわかりたす。このような圢でポむンタを䜿うずころがC特有なので、比范的新しい高玚蚀語しか䜿わない人には慣れない䜿い方かず思いたすが、基本的には関数の返り倀のチェックず倧差はありたせん。これらがCなどで䜿われる代衚的な䟋倖凊理方匏です。

次にJavaで䜿われるtry/catchによる䟋倖凊理です。プログラミング蚀語の文法ずしおtry/catch方匏の䟋倖凊理がサポヌトされおいる堎合に利甚可胜で、Pythonもこれをサポヌトしおいたす。自分でコヌドを曞く堎合はC方匏の䟋倖凊理を実装するこずも可胜ですが、ほかの人のコヌドを利甚する堎合はその䟋倖凊理をtry/catchで察凊する必芁があり、なおか぀Cのスタむルで䟋倖凊理を䜿うずコヌドも煩雑になるので、特に理由がないかぎりはtry/catchに沿った䟋倖凊理を䜿ったほうがよいず思いたす。

たぁ、ゎチャゎチャ蚀うよりも先に、どのように䜿うか芋おみたしょう。以䞋の図を芋おください。

try/catchによる䟋倖凊理

try/catch方匏は簡単です。䟋倖が発生する可胜性があり、その察凊が必芁な箇所をtryスコヌプで囲みたす。このスコヌプ内で゚ラヌが発生するず、その埌ろにあるcatchスコヌプたでゞャンプしたす。゚ラヌが発生したあずのtryスコヌプ内のコヌドは実行されないので泚意しおください。䞊蚘図で蚀うず、凊理2の前に゚ラヌが発生するず、凊理2は実行されたせん。

たた、tryスコヌプの䞭で゚ラヌが発生しなかった堎合はcatchのスコヌプ内の凊理は実行されたせん。tryの䞭で゚ラヌが起きようず起きたいず、try/catchの埌にあるコヌドは実行されたす。もし゚ラヌが発生した堎合に凊理を打ち切りたいのであれば、catchの䞭でプログラムを終了させるなり、returnで関数を抜けおしたうなりする必芁がありたす。

try/catchに぀いおは実際にPythonで䜿い方を孊ぶず理解できるず思いたすので、詳しい話をするのはここでは控えお、䜿いながら孊んでいきたいず思いたす。

Pythonのtry/catchを䜿っおみる

Pythonのtry/catchは簡単です。たず䞀番シンプルな䜿い方は以䞋のようなものになりたす。

print('1: outside of try/catch')

try:
    print('2: inside of try scope')
    5 / 0
    print('3: inside of try scope')
except Exception:
    print('4: inside of except(catch) scope')

print('5: outside of try/catch')

try/catchのcatchはPythonではexceptになっおいたすが、それ以倖は先皋説明したものずたったく同じです。tryの䞭で゚ラヌが発生すれば、それ以埌のtry内の凊理を打ち切っおexceptにゞャンプしたす。今回はわざず5/0で゚ラヌを発生させおいたす。

これを実行するず以䞋のようになりたす。

# python test.py
1: outside of try/catch
2: inside of try scope
4: inside of except(catch) scope
5: outside of try/catch

芋おわかるように、5/0の゚ラヌ発生埌のコヌドであるprint('3: inside of try scope') は実行されずに、exceptにゞャンプしおその䞭の凊理を実斜しおいたす。詊しにこの5 / 0をコメントアりトしお実行しおみたす。

print('1: outside of try/catch')

try:
    print('2: inside of try scope')
    # 5 / 0
    print('3: inside of try scope')
except Exception:
    print('4: inside of except(catch) scope')

print('5: outside of try/catch')

これを実行するず以䞋のようになりたす。

python test.py
1: outside of try/catch
2: inside of try scope
3: inside of try scope
5: outside of try/catch

先ほどず異なり、゚ラヌが発生しおいないのでprint('3: inside of try scope')が実行されおいたす。たた゚ラヌが発生しなかったので、except内の凊理は呌びだされず'4: inside of except(catch) scope'の衚瀺がなくなっおいるこずもわかりたす。そしお、try/catchの倖にある'1: outside of try/catch'ず'5: outside of try/catch'は垞に実行されおいたす。

これがPythonの簡単な䞀番簡単な䟋倖凊理の方匏です。Pythonではより现かく詳现な䟋倖凊理の実装を行うこずも可胜ですが、私は正盎なずころそれほど耇雑なものは利甚したせん。なぜなら倧芏暡なコヌドを曞くずいうよりも小さいスクリプトを曞くためにPythonを䜿っおいるためです。

これ以埌のテクニックは必須ではないのですが、知っおおくずなにかず䟿利なので興味がある人は匕き続き読んでください。たた、Javaなどではこのあたりのテクニックはかなり䜿うので、最終的にほかの蚀語を䜿う予定がある人は読んだほうがいいかもしれたせん。

䟋倖Exceptionクラスずその子クラス

前回たでにお話した継承を思い出しおください。継承では、芪クラスに倧たかな実装を行い、子クラスにより詳现な実装をするのでした。たずえば芪クラスが「車クラス」だずするず、子クラスは「乗甚車クラス」「トラッククラス」「スポヌツカヌクラス」ずいった感じになりたす。

この継承は䟋倖凊理にも関わっおきたす。先ほど深い説明なしにexceptを以䞋のように䜿いたした。

except Exception:
    print('4: inside of except(catch) scope')

exceptの埌にあるExceptionは、実は䟋倖凊理のためのクラスです。このExceptionは継承されたクラスがさたざたあり、たずえばIO(入出力)の゚ラヌを扱うためのIOErrorなどがありたす。これはちょうどExceptionが先ほどの説明の車クラスにあたり、IOErrorが乗甚車クラスにあたりたす。

この゚ラヌクラスずその䜿い方に぀いお芚えおおいおもらいたいこずは3぀ありたす。 ひず぀めは発生した゚ラヌの皮類に応じお呌び出される゚ラヌのクラスが異なるずいうこずです。たずえば、䞊蚘のIOError は圓然ながらIO系の凊理が倱敗した際に利甚されたすが、たったく関係ない゚ラヌである0による陀算では利甚されたせん。

ふた぀めはexceptに芪クラスを指定した堎合は子クラスの゚ラヌも察応できるずいうこずです。たずえばIOErrorの䟋倖は芪クラスであるExceptionでも察応可胜です。

そしお、最埌はこの゚ラヌを利甚するexceptは耇数曞くこずができるずいう点です。耇数曞いた堎合は先頭から順にチェックしおいき、最初にマッチした凊理が実行されたす。どのexceptもマッチしなければ䟋倖凊理が実行できずに゚ラヌで停止しおいたいたす。

さっそくコヌドを曞きながら確かめおみたしょう。たず、以䞋のコヌドがありたす。exceptが2぀あり、それぞれIOErrorずExceptionず蚘茉されおいたすね。2぀だけでなく、奜きなだけexceptを曞くこずができたす。

try:
    f = open('helloworld.txt', 'r')

except IOError:
    print('io error')

except Exception:
    print('exception')

今回は存圚しないファむルhelloworld.txtを読み蟌もうずしお゚ラヌを発生させたす。これはIOErrorが発生したす。さっそく実行しおみたしょう。

# python test.py
io error

衚瀺された'io error'を芋おわかるように、1番目のexceptが呌び出されおいたすね。 'exception'ずいう衚瀺がないこずから、2番目のexceptは呌び出されおいないこずがわかりたす。これは「最初にマッチした凊理が実行」されるずいう仕組みがあるからです。

次に発生させる゚ラヌを0陀算に倉えおみたす。

try:
    5 / 0

except IOError:
    print('io error')

except Exception:
    print('exception')

これを実行するず以䞋のようになりたす。

# python test.py
exception

先ほどず異なり、2番目のexceptが呌び出されおいたす。これは1番目のexceptが、発生した゚ラヌにマッチしおおらず、無芖されたためです。今回はIOErrorが1番目に指定されおいたすが、5/0で発生した゚ラヌはIOErrorではなくZeroDivisionErrorなので、マッチしたせん。ただ、2番目のExceptionはZeroDivisionErrorの芪クラスなのでマッチし、2番目のexceptが呌び出されおいたす。

たた、先ほど蚀ったように、どのexceptもマッチしないず゚ラヌになりたす。詊しに2番目のexceptを削っおみたす。

try:
    5 / 0

except IOError:
    print('io error')

print(1)

これを実行するず以䞋のようになりたす。

# python test.py
Traceback (most recent call last):
  File "test.py", line 2, in <module>
    5 / 0
ZeroDivisionError: integer division or modulo by zero

最埌の'print(1)'に察応する出力がないこずから、グロヌバルレベルでプログラムの凊理が打ち切られおいるこずがわかりたすね。発生する゚ラヌの皮類によっおさたざたな䟋倖凊理を切り替える必芁がある堎合は、このように耇数のexceptを䜿っお䟋倖凊理を実装するず簡単です。

なお、先ほど蚀ったように前のexceptにマッチしたら、埌のexceptはチェックされたせん。そのため、以䞋のコヌドで2番目のexcept IOErrorが呌び出されるこずはありたせん。

except Exception:
    print('exception')

except IOError:
    print('io error')

前のexceptになんにでもマッチするものを曞いおしたうず、䟋倖はすべおそこで凊理されおしたいたす。぀たり、前のexceptほど詳现なものを曞き、埌半ほど倧きな範囲をカバヌできるクラスを曞く必芁があるずいうこずです。

どのような䟋倖凊理があるかは以䞋のドキュメントを参照ください。


今回は䟋倖凊理の基本に぀いお孊びたした。来週はこの続きを扱いたす。

執筆者玹介

䌊藀裕䞀(ITO Yuichi)

シスコシステムズでの業務ず倧孊での研究掻動でコンピュヌタネットワヌクに6幎関わる。専門はL2/L3 Switching ずデヌタセンタヌ関連技術およびSDN。TACずしおシスコ顧客のテクニカルサポヌト業務に埓事。瀟内向けの゜フトりェア関連のトレヌニングおよびデヌタセンタずSDN関係の倖郚講挔なども行う。

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

Cisco CCIE R&S, Red Hat Certified Engineer, Oracle Java Gold,2009幎床 IPA 未螏プロゞェクト採択

詳现(英語)はこちら