Python contextlib.ExitStack

Python学習【365日チャレンジ!】270日目のマスターU(@Udemy11)です。

最近、車の中ではいつも坪田信貴氏の「才能の正体」を聴いています。

数少ない著者が朗読をするオーディオブックで、AmazonのAudibleの無料体験でゲットしました。

もうどのくらい繰り返したかわかりませんが、本の内容がかなり頭の中に入ってくる上、まるで坪田さんと知り合いになった感じがしてくるので、オーディオブックは超お勧めです。

ハリーポッターと死の秘宝と謎のプリンスの英語朗読版も聴いていたのですが、これまた英語の勉強になるのでやっぱりAudibleはお勧めです。

1冊の本の値段が高いのですが、無料体験で1冊ダウンロードしたあと解約すればずっと聴くことができるので、ぜひ無料体験してみてください。

それでは今日もPython学習を始めましょう。

昨日の復習

昨日は、stdoutstderrを使ったファイル出力を学習しました。

どちらもシステムのストリームで値を保持するので、そのまま出力することも可能でしたが、contextlib.redirect_stdoutcontextlib.redirect_stderrを使ってファイルに値を書き出すことが可能でした。

詳細は昨日の記事をごらんください。

今日は、例外処理に関するExitstackを学習します。

例外処理で処理を分岐

try-except文を使うことでエラーによる条件分岐を扱うことができましたが、エラーが起こっても起こらなくても処理をさせるやり方がtry-finally文です。

try-finally文を使ったコードを書いてみましょう。

def is_ok_job():
    try:
        print('Do something')
        return True
    except Exception:
        return False

def cleanup():
    print('Clean up')

try:
    is_ok = is_ok_job():
    print('More tasks')
finally:
    if not is_ok:
        cleanup()

復習がてら、このコードを読み解いてみてください。

参考までに出力結果は次のようになります。

出力結果

Do something
More tasks

1行目から6行目の関数is_ok_jobは、Do somethingと出力したあと、Trueを返すか、エラーが起こってFalseを返す関数です。

8行目の関数cleanupは、Clean upを出力する関数です。

11行目からの実行コードは、関数is_ok_jobの返り値を変数is_okに代入して、More tasksを出力したあと、is_okFalseの場合のみ、関数Clean upを実行します。

上記のコードでは、エラーが起こってis_ok_jobの返り値がFalseにはならないため、関数cleanupは実行されません。

4行目にraise Exception('Error')を挿入してエラーを起こさせると出力結果は、最後にClean upの出力が追加されます。

出力結果

Do something
More tasks
Clean up

ExitStack

このtry-finally文とおなじ処理をすることができるのが、contextlib.ExitStackです。

import contextlib

def is_ok_job():
    try:
        print('Do something')
        return True
    except Exception:
        return False

def cleanup():
    print('Clean up')

with contextlib.ExitStack() as stack:
    stack.callback(cleanup)

    is_ok = is_ok_job()
    print('More tasks')

    if is_ok:
        stack.pop_all()

最初にcontextlibをインポートして、3行目から11行目までは同じコードを記述しています。

13行目14行目でwithステートメントでcontextlib.ExitStackを使い、最後に実行する関数cleanupcallbackに入れています。

16行目、17行目は先ほどのコードと一緒です。

19行目で、変数is_okTrueかどうかを判断して、Trueの場合は、stack.callbackpop_allで空にします。

出力結果

Do something
More tasks

このコードの場合、is_okTrueになっているので、stack.callbackが空になって関数cleanupが実行されないので、Clean upは出力されていません。

try-finallyのときと同じように、6行目にraise Exception('Error')を挿入すれば、エラーが起こってis_okFalseとなるので、stack.callbackに入れられた関数cleanupが実行され、Clean upが最後に出力されます。

出力結果

Do something
More tasks
Clean up

まとめ

コードを読み解くのはほんとパズルみたいな感じがしますが、すべてのコードが理解できて一連の流れがつながったときは、ボーリングでストライクを取ったときのような快感を感じることができます。

理解できないときは、ストライクスポットに入っているのに、なぜか1本だけ残ってしまい、スペアすらとれないもどかしさを感じてしまいます。

プログラミングはボーリングのように体力を使わないので、ボーリングと違って何度でも繰り返して試すことができますので、納得いくまで何度も実行してみてください。

今回はpop_allstack.callbackに入れた関数を空にするのではなく、すべて実行すると勘違いしてしまい、ちょっと戸惑ってしまいました。

でもpopは、リストでも値を取り出して解放していたことを思い出せたので、早いうちに間違いに気づくことができました。

ほんと復習は大切ですね。

ということで、明日もGood Python!