Python 3.10では、もう一つ、文法が変更されました。この修正、実は Python3.9 ですでに導入されていたのですが、隠し機能的な扱いでドキュメントには記載されていませんでした。このサイトでは以前にこの機能を紹介していましたが、ここで新しい解説を追加して再掲します。
Pythonのwith文¶
Pythonでは、ある一定の期間だけオブジェクトを使用したり、いろいろな設定を行って用事がすんだら元に戻したい、という処理を行うとき、with
文を使用します。
たとえば、ファイルを読み込むときには、with
文を利用して、作成したファイルオブジェクトを自動的に開放するようにします。
with open("hello.txt") as hello:
print(hello.read())
with
文でオープンしたファイルオブジェクト hello
は、with
ブロックを終了すると自動的にクローズされます。これは、try
〜 finally
を使って次のようにも書けます。
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で常にこの書き方が利用できるようになりました。