📖

なんとなく使っていたPythonのwith文の仕組みと使い方を調べてみた!

2023/12/18に公開

こんにちは、AIQ株式会社のフロントエンドエンジニアのまさぴょんです!
今回は、なんとなく使っていたPythonのwith文の仕組みと使い方を調べてみたので、解説します。

with文、なんとなく使っていませんか?

Pythonを使っていると結構な頻度で、目にするのが、with構文だと思います。
他の言語で、withというkeywordを見たことがない私にとっては、最初に見た時は「これ何?」って、なりました。
その時は、ちょっと調べて、ファイル操作の時にこれを使えばいいのね〜ぐらいの感じで使っていました。

with open('sample.txt', 'r') as f:
    result = f.read()
    print(result)

with文の仕組みとは?

ファイル操作のための構文ぐらいに思ってしまっていた、with文ですが、ちゃんと仕組みを調べてみたら、次のような事実が判明しました。

  1. with文は最初に何か(前処理)して最後に必ず何か(後処理)する(try/finally文)の処理をまとめたもの。

    • 「最初と最後に何かをする」は、コンテキストマネージャ と呼ばれる__enter__()__exit__()の2つのメソッドを持つ型で表される。
  2. with文のwith xxxxxx部分は、コンテキストマネージャを指している。

    • as xxの部分は、実行結果の返り値(大抵は、そのClassのインスタンス)を受け取っている。
  3. ファイルオブジェクトなど、一部の組み込みClassは、コンテキストマネージャだからwith文が使える。

  4. with文に対応するClassは、ユーザー定義で作成することができる。

    • __enter__()__exit__()の2つのメソッドを追加すれば、どんなObjectwith文対応のClassにすることができる。
with open('sample.txt', 'r') as f:
    result = f.read()
    print(result)

with文の使い方(with文を使わない場合と使う場合のファイル操作)

with文の仕組みの概要を理解したところで、with文を使わない場合と使う場合のファイル操作を例にCodeを見ていきたいと思います。

with文を使わない場合のファイル操作

with文を使用しない場合は、次のように、定型の前処理・後処理が必要で、Codeが長くなってしまいます。

no_with.py
# 1. with文を使用しない場合: 定型の前処理・後処理が必要で、Codeが長くなる。
try:
    # 前処理: ファイルを開く処理
    f = open('sample.txt', 'r')

    # 本処理: ファイルの内容を読み込む
    result = f.read()
    print(result)

finally: # finally は、処理の成功/失敗に関わらず、必ず実行する処理を記述する
    # 後処理: ファイルを閉じる
    f.close()

with文を使う場合のファイル操作

ファイルを開くopen()という関数はwith文の処理に対応しているので、次のように書き直すことができます。

# 2. with文を使用する場合: 定型の前処理・後処理が省略されるので、Codeがコンパクトになる。
with open('sample.txt', 'r') as f:
    result = f.read()
    print(result)

2つを比較した結果から、定型の前処理と後処理を省略したいときは、with文を活用するといいことがわかります。

with文に対応する Class を定義する

with文に対応するようなObjectは、次のように__enter__()__exit__()の2つのメソッドを追加すれば、自分でも独自に作成することができます。

class CustomFileReader:
    def __init__(self, filename):
        print('init Block')
        self.filename = filename
        self.file = None

    # 1. __enter__(): with文で利用されるオブジェクトの前処理を定義する
    def __enter__(self):
        """ with文で最初に呼ばれる処理 """
        print('enter Block')
        self.file = open(self.filename, 'r')
        return self.file

    # 2. __exit__(): with文で利用されるオブジェクトの後処理を定義する
    def __exit__(self, exc_type, exc_value, traceback):
        """ withブロックから抜ける時の処理 """
        print(f'exit Block: {exc_type}, {exc_value}, {traceback}')
        self.file.close()

if __name__ == '__main__':
    with CustomFileReader('sample.txt') as f:
        print(f.read())

__enter__()__exit__()の2つのメソッドの仕組みをまとめると次のようになります。

まとめ

Pythonのwith文は、処理の前処理と後処理を設定する(__enter__()__exit__()を追加する)ことで、その処理をよりコンパクトに利用できるようにする書き方であることがわかりました。

定型の処理の前処理と後処理を省略したいときは、with文を活用してみようと思いました。

個人で、Blogもやっています、よかったら見てみてください。

https://masanyon.com/

注意事項

この記事は、AIQ 株式会社の社員による個人の見解であり、所属する組織の公式見解ではありません。

求む、冒険者!

AIQ株式会社では、一緒に働いてくれるエンジニアを絶賛、募集しております🐱🐹✨

詳しくは、Wantedly (https://www.wantedly.com/companies/aiqlab)を見てみてください。

参考・引用

https://djangobrothers.com/blogs/with_statement_basic/

https://zenn.dev/k41531/articles/9c566a778b79ca

https://docs.python.org/ja/3/library/stdtypes.html#context-manager-types

AIQ Tech Blog (有志)

Discussion