Python の with 文で複数のリソースを扱う

複数のリソースを同時に扱いたい場合、with 文にはいくつかの書き方があります。状況に応じて使い分けましょう。

カンマ区切りで複数指定

最もシンプルな方法は、カンマで区切って複数のコンテキストマネージャを指定することです。

with open("input.txt", "r") as infile, open("output.txt", "w") as outfile:
    for line in infile:
        outfile.write(line.upper())

この書き方では、すべてのリソースが同時に確保され、ブロック終了時にすべて解放されます。

括弧を使った複数行表記

Python 3.10 以降では、括弧を使って複数行に分けて書けます。

with (
    open("input.txt", "r") as infile,
    open("output.txt", "w") as outfile,
    open("log.txt", "a") as logfile
):
    for line in infile:
        outfile.write(line.upper())
        logfile.write(f"処理: {line}")

リソースが多い場合に読みやすくなります。

バックスラッシュによる継続(古い方法)

Python 3.9 以前では、バックスラッシュで行を継続する方法が使われていました。

with open("input.txt", "r") as infile, \
     open("output.txt", "w") as outfile:
    for line in infile:
        outfile.write(line.upper())

この書き方は今でも動作しますが、括弧を使う方法の方が推奨されます。

ネストした with 文

with 文をネストすることもできます。

with open("input.txt", "r") as infile:
    with open("output.txt", "w") as outfile:
        for line in infile:
            outfile.write(line.upper())

ただし、インデントが深くなるため、カンマ区切りの方が一般的に好まれます。

リソースの解放順序

複数のリソースは、指定した順序と逆の順序で解放されます。

class Resource:
    def __init__(self, name):
        self.name = name
    
    def __enter__(self):
        print(f"{self.name}: 確保")
        return self
    
    def __exit__(self, *args):
        print(f"{self.name}: 解放")

with Resource("A"), Resource("B"), Resource("C"):
    print("処理中")

# 出力:
# A: 確保
# B: 確保
# C: 確保
# 処理中
# C: 解放
# B: 解放
# A: 解放

これは「後入れ先出し」(LIFO)の順序で、スタック的な動作になります。

実用例:ファイルのコピー

複数ファイルを扱う典型的な例として、ファイルコピーがあります。

def copy_file(src, dst):
    with open(src, "rb") as fsrc, open(dst, "wb") as fdst:
        while chunk := fsrc.read(8192):
            fdst.write(chunk)

copy_file("original.dat", "backup.dat")

入力と出力を同時に開くことで、効率的にコピーできます。どちらかでエラーが発生しても、両方のファイルが確実に閉じられます。