🥤

[Python] 条件付きwith・複数リソースの一括解放

2022/08/31に公開

筆者の環境・参考文献

  • Python 3.10
    • 本記事の内容は3.7以降なら使えるはず (nullcontext)

一言でいえば、contextlibを勉強しなければと思いました。

内容は以下をただ書き直しているだけになりますが:

課題1: 条件付きwith

特定の条件でだけwithを書きたい場面がたまにあります。withにNoneは入れられません。どうすればよいか。

def foo(flag: bool) -> None:
    file = None
    if flag:
        file = open("out.txt", "w")

    ...
    
    if file:
        file.write("Hello")
	
    ...
    
    if file:
        file.close()

contextlib.nullcontext を使えば、withに入れ込むことができます。nullcontextが選ばれた場合、以下のコードでのfileNoneになります。

from contextlib import nullcontext

def foo(flag: bool) -> None:
    with open("out.txt", "w") if flag else nullcontext() as file:
        ...
        if file:
            file.write("Hello")

これは以下のコードと同じです。__enter__がNoneを返すので、fileがNoneになるわけです。

class my_null_context:
    def __enter__(self):
        return None
    def __exit__(self, exc_type, exc_value, traceback):
        return False


def foo(flag: bool) -> None:
    with open("out.txt", "w") if flag else my_null_context() as file:
        ...

課題2: 複数リソースの一括解放

たくさんの要解放リソースを一気にインスタンス化したい場面がたまにあります。どうすればよいか。

file_names = ["foo.txt", "bar.txt", "baz.txt"]

handles = []
try:
    handles = [open(f, "w") for f in file_names]
    for h in handles:
        h.write("Goodbye")
    
finally:
    for h in handles:
        h.close()

contextlib.ExitStackが便利です。公式ドキュメントで一番に載っている例ですが。

from contextlib import ExitStack

file_names = ["foo.txt", "bar.txt", "baz.txt"]

with ExitStack() as stack:
    handles = [stack.enter_context(open(f, "w")) for f in file_names]
    for h in handles:
        h.write("Goodbye")  # enter_contextで包んでいますが、意識せず同様に使えます。

Discussion