0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Python】with句の中で動いている処理

Posted at

with句の一般用途

主にテキストファイルを読み書きする際なんかに使用されているのが一番イメージしやすいかなと思います。

with open("hoge.txt","r") as f :
    for line in f :
        print(line)

簡単に言ってしまえば 「後片付け」 と 「エラー処理」 を勝手にやってくれる便利機能です。
もちろん文法として用意されているものですから open() 以外にもいろんな処理で使用することができます。

with句が利用できる処理

クラスの中に __enter__() と __exit__() を持っているオブジェクトで利用可能です。
名前からある程度推察できるかと思いますが、with句が呼び出されたときに__enter__()が実行され、終了したときに__exit__()が実行されるという仕組みです。

試しに__enter__()__exit__()が存在しない関数でwith句を使用してみるとエラーになります。

class Hoge():
    def __init__(self):
        pass
    def execute(self):
        print("hoge")

with Hoge() as hoge:
    hoge.execute()
TypeError: 'Hoge' object does not support the context manager protocol

正常に動くパターンは後述します。

with句の中身

具体例としてThreadPoolExecutorを見ていきます。
以下の通りThreadPoolExecutorは __enter__() と __exit__() を持っていることを確認できます。

from concurrent.futures import ThreadPoolExecutor
print(dir(ThreadPoolExecutor))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_adjust_thread_count', '_counter', '_initializer_failed', 'map', 'shutdown', 'submit']

__enter__()とは

with句の中で使用するオブジェクトを返却します。
返却されたオブジェクトはasの後に定義された変数に格納されます。
※ with open("hoge.txt","r") as f :でいうfの部分

返却するオブジェクトは以下の2通りがあります。
 1. 自分自身(self)
 2. 関連する別オブジェクト

ThreadPoolExecutor(正確には親クラスのExecutor)ではselfを返却しています。(open()も同様です。)

def __enter__(self):
    return self

関連する別オブジェクトを返却するパターンの代表例としてはdecimal.localcontextが挙げられます。
クラス自体は_ContextManagerですが、__enter__()が返却しているのはContext型のオブジェクトを返却していますね。

_decimal.pyi
class _ContextManager:
    new_context: Context
    saved_context: Context
    def __init__(self, new_context: Context) -> None: ...
    def __enter__(self) -> Context: ...
    def __exit__(self, t: type[BaseException] | None, v: BaseException | None, tb: TracebackType | None) -> None: ...

__exit__()とは

with句を抜ける際に実行される処理です。異常終了の場合も実行されます。
そういう点ではfinally的な位置づけだと思ってください。

ThreadPoolExecutorの場合はshutdown()を呼び出した後でFalseをリターンしています。

def __exit__(self, exc_type, exc_val, exc_tb):
    self.shutdown(wait=True)
    return False

冒頭で記述した 「後片付け」 の処理がThreadPoolExecutorの場合はshutdown()ということですね。

また、この関数には引数と戻り値があります。

引数について 本関数は3つの引数を受け取れるように実装しなければなりません。

 第一引数 : 例外の型
 第二引数 : 例外の値
 第三引数 : トレースバックオブジェクト

正常終了の場合は、各引数にNoneが設定されます。

def __exit__(self, exc_type, exc_val, exc_tb):
    print("exc_type : ", exc_type)
    print("exc_val  : ", exc_val)
    print("exc_tb   : ")
    import traceback
    traceback.print_tb(exc_tb)
exc_type :  <class 'TypeError'>
exc_val  :  HogeによるTypeErrorが発生しました
exc_tb   :
  File "with.py", line 24, in <module>
    raise TypeError("HogeによるTypeErrorが発生しました")
戻り値について

ThreadPoolExecutor__exit__()にもある通り、本関数はBooleanを返却します。
(省略された場合はFalseの挙動になります)

これはwith句の中でエラーが発生した時の挙動に影響するもので、Trueが返却された場合は発生したエラーを握りつぶしてwith句終了後から後続処理を続行します。

実例

上記の内容を踏まえて自作のクラスを作ってみます。

正常系

class Hoge():
    def __init__(self):
        print(1)

    def __enter__(self):
        print(2)
        return self

    def execute(self):
        print(4)
        print("hoge")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(6)


with Hoge() as hoge:
    print(3)
    hoge.execute()
    print(5)
print(7)
1
2
3
4
hoge
5
6
7

実行結果からわかるように
 1. __init__()の実行
 2. __enter__()の実行
 3. with句の中身を実行
 4. with句が終了するタイミングで__exit__()の実行
 5. with句外の後続処理を実行
という順番で動いています。

例外発生時

class Hoge():
    def __init__(self):
        print(1)

    def __enter__(self):
        print(2)
        return self

    def execute(self):
        print(4)
        print("hoge")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(6)
+       return False


with Hoge() as hoge:
    print(3)
+   raise TypeError
    hoge.execute()
    print(5)
print(7)
1
2
3
6
Traceback (most recent call last):
  File "with.py", line 20, in <module>
    raise TypeError
TypeError

例外発生以降の後続処理は行われずエラー終了しますが、__exit__()の処理は実行されているのがわかります。

例外を握りつぶす場合

class Hoge():
    def __init__(self):
        print(1)

    def __enter__(self):
        print(2)
        return self

    def execute(self):
        print(4)
        print("hoge")

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(6)
-       return False
+       return True


with Hoge() as hoge:
    print(3)
    raise TypeError
    hoge.execute()
    print(5)
print(7)
1
2
3
6
7

例外発生以降の処理は実行されていませんがエラーは出力されず、with句の後から処理が再開しているのがわかります。

参考資料

上記記事は公式のリファレンスを元に作成しました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?