Python 3.9 の with文

2021-02-03

Pythonでは、ある一定の期間だけオブジェクトを使用したり、いろいろな設定を行って用事がすんだら元に戻したい、という処理を行うとき、with文を使用します。

たとえば、ファイルを読み込むときには、with 文を利用して、作成したファイルオブジェクトを自動的に開放するようにします。

with open("hello.txt") as hello:
    print(hello.read())

with 文でオープンしたファイルオブジェクト hello は、with ブロックを終了すると自動的にクローズされます。これは、tryfinally を使って次のようにも書けます。

hello = open("hello.txt")
try:
    print(hello.read())
finally:
    hello.close()

複数のリソース

with 文を使うとき、ファイルが一つずつなら簡単なのですが、複数のファイルを同時に利用したいときにはちょっと面倒になります。with 文をネストすると、複数のオブジェクトを同時に管理できます。

with open("file1") as file1:
    with open("file2") as file2:
        with open("file3") as file3:
            print(file1.read(), file2.read(), file3.read())

しかし、この方法ではオブジェクトが一つ増えるごとに with 文でインデントされることになり、ちょっと数が多くなるとすぐに書きにくくなってしまいます。

with 文には、複数の expr as var をカンマで区切って指定できます。この方法だと、次のように書けます。

with open("file1") as file1, open("file2") as file2, open("file3") as file3:
    print(file1.read(), file2.read(), file3.read())

この方法ならインデントは深くなりませんが、with 文の行が非常に長くなってしまいます。適当なところで改行したいですが、勝手に改行すると SyntaxError になってしまいます。

# これはSyntaxError

with open("file1") as file1, 
     open("file2") as file2,
     open("file3") as file3:

    print(file1.read(), file2.read(), file3.read())

行末に \ 記号を指定すれば改行できます。

# これはOK

with open("file1") as file1, \
     open("file2") as file2, \
     open("file3") as file3:

    print(file1.read(), file2.read(), file3.read())

しかし、いちいち行末に \ を入れるのは面倒ですし、見た目にもあまり美しくありません。

以前は contextlib.nested()

from contextlib import nested
with nested(
    open("file1"),
    open("file2"),
    open("file3"))
as (file1, file2, file3):
    print(file1.read(), file2.read(), file3.read())

と書けたのですが、cotextlib.nested() には 問題 があり、Python3.1でDeprecateされてしまいました。

Python 3.3では、contextlib.ExitStack が追加され、つぎのように書けるようになりましたが、正直今一つです。

from contextlib import ExitStack 

with ExitStack() as stack:
    file1 = stack.enter_context(open("file1"))
    file2 = stack.enter_context(open("file2"))
    file3 = stack.enter_context(open("file3"))

Python3.9以降では

Python3.9では、次のように( ) を使って、with文に複数のオブジェクトを指定できるようになりました。この形式なら自由に改行もできます。

with (open("file1") as file1, 
      open("file2") as file2,
      open("file3") as file3):

    print(file1.read(), file2.read(), file3.read())

え? だったら最初からそうしておけば良かったんじゃないの?

たしかにそうなんですが、Pythonでは単純に

with (var, var2, var3):

のようにカッコを付けた形式で記述すると、これはタプルオブジェクトを作成する式になってしまいます。with 文だけを特別扱いできればよかったのですが、これは従来のPythonのしくみでは実現が困難な構造になっていました。

しかし、Python 3.9 ではPython言語のパーザが置き換えられ、こういった記述が可能となりました。そこで上記のように カッコを使って with 文を囲めるように変更され、Python3.9に追加されました。

Python3.9では、この変更は新しいパーザを利用しているときのみ、有効となります。python3 -X oldparser のように、旧パーザを使用するように指定した場合は従来と同様に SyntaxError となります。このため、この機能はまだ正式にはドキュメントに記述されていませんでした。

Python3.10からは新しいパーザのみ利用可能となるため、3.10以降のPythonで常にこの書き方が利用できるようになりました。


Copyright © 2001-2023 python.jp Privacy Policy python_japan
Amazon.co.jpアソシエイト
Amazonで他のPython書籍を検索