Pythonの「with」ステートメントはどのように設計されていますか?


419

with今日、初めてPython ステートメントに出会いました。Pythonを数か月間軽く使用していて、その存在すら知りませんでした!そのややあいまいなステータスを考えると、私は尋ねる価値があると思いました:

  1. Python withステートメントは何のために設計されたものですか?
  2. あなたはそれを何に使っているの?
  3. 私が知っておく必要がある落とし穴、またはその使用に関連する一般的なアンチパターンはありますか?それはよりよい使用である任意の例try..finallyよりもwith
  4. なぜもっと広く使われないのですか?
  5. どの標準ライブラリクラスと互換性がありますか?

5
参考までに、ここwithにPython 3のドキュメントがあります。
Alexey

Javaのバックグラウンドから来ているため、たとえそれが完全に正しくない場合でも、Javaの対応する「リソースを使って試す」としてそれを覚えておくのに役立ちます。
-vefthym

回答:


399
  1. 私はこれが私の前に他のユーザーによってすでに回答されていると思うので、完全を期すためにのみ追加します。このwithステートメントは、一般的な準備とクリーンアップタスクをいわゆるコンテキストマネージャーにカプセル化することで例外処理を簡素化します。詳細については、PEP 343を参照してください。たとえば、openステートメントはそれ自体がコンテキストマネージャーであり、ファイルを開き、実行がwith使用したステートメントのコンテキストで実行されている限りファイルを開いたままにし、コンテキストを離れたらすぐに閉じることができます。例外のために、または通常の制御フロー中に、それを離れたかどうかに関係なく。したがって、withステートメントはC ++のRAIIパターンと同様の方法で使用できます。一部のリソースは、withステートメントとあなたがwithコンテキストを離れるとリリースされます。

  2. たとえばwith open(filename) as fp:、を使用してファイルを開く、を使用してロックを取得するwith lock:(はのlockインスタンスですthreading.Lock)。のcontextmanagerデコレータを使用して、独自のコンテキストマネージャを構築することもできますcontextlib。たとえば、現在のディレクトリを一時的に変更してから元の場所に戻る必要がある場合に、これをよく使用します。

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory

    ここでは、一時的にリダイレクトすることをもう一つの例だsys.stdinsys.stdoutsys.stderrいくつかの他のファイルハンドルにし、後でそれを復元は:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"

    そして最後に、一時フォルダーを作成し、コンテキストを離れるときにそれをクリーンアップする別の例:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want

20
RAIIとの比較を追加していただきありがとうございます。私が知る必要があるすべてを私に言ったC ++プログラマーとして。
Fred Thomsen 2014

わかりましたので、これを明確にします。withステートメントは、その下の指示が完了するまで変数にデータを入力し、その後変数を解放するように設計されていると言っていますか?
Musixauce3000 2016年

pyスクリプトを開くために使用したからです。with open('myScript.py', 'r') as f: passfこれは、ドキュメントをf通常のopenステートメントで割り当てた場合に表示される内容であるため、変数を呼び出してドキュメントのテキストコンテンツを表示できると期待していましたf = open('myScript.py').read()。しかし、代わりに私は以下を得ました:<_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>。どういう意味ですか?
Musixauce3000 2016年

3
@ Musixauce3000-を使用withしてもread、実際のファイルを削除する必要はありません。with呼び出しは、open-それはあなたがそれを行うために必要なものを知っていない-あなたは、インスタンスのために追求したいと思うかもしれません。
トニーサフォーク66

@ Musixauce3000 withステートメントは、変数をデータで埋めるか、その下の指示が完了するまで環境にその他の変更を加えてから、必要なあらゆる種類のクリーンアップを実行できます。実行できるクリーンアップの種類は、開いているファイルを閉じる、またはこの例では@Tamasが行ったように、ディレクトリを以前の場所に戻すなどです。Pythonにはガベージコレクションがあるため、変数を解放することは重要ではありません使用事例。 with通常、他の種類のクリーンアップに使用されます。
Bob Steinke、

89

2つの興味深い講義を提案します。

  • PEP 343 "with"ステートメント
  • Effbot Pythonの「with」ステートメントを理解する

1.with文はコンテキストマネージャによって定義された方法とブロックの実行をラップするために使用されます。これにより、一般的なtry...except...finally使用パターンをカプセル化して簡単に再利用できます。

2. 次のようなことができます。

with open("foo.txt") as foo_file:
    data = foo_file.read()

または

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

または(Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

または

lock = threading.Lock()
with lock:
    # Critical section of code

3. ここにアンチパターンはありません。Pythonへの飛び込みの
引用:

try..finallyは良いです。と良いです。

4.try..catch..finally他の言語のステートメント を使用することは、プログラマの習慣に関連していると思います。


4
スレッド化された同期オブジェクトを処理しているとき、それは本当にそれ自身に入ります。Pythonでは比較的まれですが、必要な場合は本当に必要withです。

1
diveintopython.orgが停止しました(永久に?)。diveintopython.netで
寄り添う

良い答えの例として、ファイルを開くは、opening、io、closeの裏側でファイル操作がカスタム参照名できれいに隠されていることを示す主要な例です
Angry 84

40

Python withステートメントはResource Acquisition Is Initialization、C ++で一般的に使用されるイディオムの組み込み言語サポートです。オペレーティングシステムリソースの安全な取得と解放を可能にすることを目的としています。

with声明は、スコープ/ブロック内のリソースを作成します。ブロック内のリソースを使用してコードを記述します。ブロックが終了すると、ブロック内のコードの結果(つまり、ブロックが正常に終了するか、例外のために終了するか)に関係なく、リソースは完全に解放されます。

withステートメントで必要なプロトコルに従うPythonライブラリの多くのリソースは、そのまま使用できます。ただし、誰でも、十分に文書化されたプロトコルを実装することにより、withステートメントで使用できるリソースを作成できます:PEP 0343

ファイル、ネットワーク接続、ロックなど、明示的に放棄する必要のあるアプリケーション内のリソースを取得する場合は常に使用してください。


27

ここでも完全を期すために、withステートメントの最も便利なユースケースを追加します。

私は多くの科学計算を行っており、一部のアクティビティでDecimalは任意精度の計算のためのライブラリが必要です。私のコードの一部には高精度が必要であり、他のほとんどの部分には精度を下げる必要があります。

私はデフォルトの精度を低い数値に設定してから、withいくつかのセクションでより正確な答えを得るために使用します。

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

私はこれを、超階数テストでよく使用します。このテストでは、多数の除算を行ってフォーム階乗を求めます。ゲノムスケール計算を行うときは、丸め誤差とオーバーフローエラーに注意する必要があります。


26

アンチパターンの例としては、ループwithwith外側に配置する方が効率的である場合にループの内側を使用する場合があります。

例えば

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

1つ目は、ファイルを1つずつrow開いて閉じる方法です。2つ目は、ファイルを1回だけ開いて閉じる方法と比べて、パフォーマンスの問題を引き起こす可能性があります。



5

ポイント1、2、および3はかなり適切にカバーされています。

4:比較的新しく、python2.6 +(またはpython2.5を使用from __future__ import with_statement)でのみ使用可能


4

withステートメントは、いわゆるコンテキストマネージャで機能します。

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

アイデアは、「with」ブロックを出た後に必要なクリーンアップを行うことにより、例外処理を簡素化することです。一部のPythonビルトインは、すでにコンテキストマネージャとして機能しています。


3

すぐに使えるサポートのもう1つの例であり、組み込みのopen()動作に慣れていると最初は少し困惑するかもしれませんが、次のconnectionような一般的なデータベースモジュールのオブジェクトです。

connectionオブジェクトは、コンテキストマネージャであり、等は、アウトボックスを使用することができるでwith-statement、しかし上記なおを使用する場合:

場合はwith-block、例外の有無にかかわらずどちらか、終了すると、接続が閉じられていません。がwith-block例外で終了した場合、トランザクションはロールバックされます。それ以外の場合、トランザクションはコミットされます。

これはwith-statementspsycopg2のドキュメントに示されているように、プログラマーが接続自体を閉じるように注意する必要があるが、接続を取得して複数で使用できることを意味します。

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

上記の例では、のcursorオブジェクトpsycopg2もコンテキストマネージャであることに注意してください。動作に関する関連ドキュメントから:

cursor終了するwith-blockと、それは閉じられ、最終的にそれに関連付けられているリソースを解放します。トランザクションの状態は影響を受けません。


3

Pythonでは通常、「with」ステートメントを使用して、ファイルを開き、ファイル内に存在するデータを処理し、さらにclose()メソッドを呼び出さずにファイルを閉じます。「with」ステートメントは、クリーンアップアクティビティを提供することで、例外処理を簡単にします。

withの一般的な形式:

with open(“file name”, mode”) as file-var:
    processing statements

注: file-var.close()でclose()を呼び出してファイルを閉じる必要はありません

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.