Pythonのキーワードwith詳細

6082 ワード

Python 2.5には、withのキーワードが追加されています.それはよく使われるtry ... except ... finally ...モードを便利に多重化する.最も古典的な例を見てみましょう
with open('file.txt') as f:
    content = f.read()

このコードでは、withのコードブロックが実行中に発生した場合にかかわらず、ファイルは最終的に閉じられます.コードブロックが実行中に例外が発生した場合、この例外が投げ出される前に、プログラムは開いているファイルを閉じます.
もう一つの例を見てみましょう.
データベース・トランザクション・リクエストを開始するときに、このようなコードが使用されることがよくあります.
db.begin()
try:
    # do some actions
except:
    db.rollback()
    raise
finally:
    db.commit()

トランザクション要求を開始する操作がwithキーワードをサポートできるようになった場合、このようなコードを使用すればよい.
with transaction(db):
    # do some actions

次に、withの実行手順を詳細に説明し、上記のコードを2つの一般的な方法で実現する.
withの一般的な実行手順
基本的なwith式で、その構造は次のとおりです.
with EXPR as VAR:
    BLOCK

ここで、EXPRは任意の式であってもよい.as VARはオプションです.一般的な実行手順は次のとおりです.
  • は、EXPRを計算し、コンテキストマネージャを取得する.
  • コンテキストマネージャの__exit()__メソッドは、その後の呼び出しのために保存される.
  • コンテキストマネージャを呼び出す__enter()__メソッド.
  • with式がas VARを含む場合、EXPRの戻り値はVARに与えられる.
  • は、BLOCKの式を実行する.
  • コンテキストマネージャを呼び出す__exit()__メソッド.BLOCKの実行中に異常が発生してプログラムが終了すると、異常のtypevalue、およびtraceback(すなわち、sys.exc_info() )がパラメータとして__exit()__メソッドに渡される.そうでなければ、3つのNoneが渡されます.

  • このプロセスをコードで表します.
    mgr = (EXPR)
    exit = type(mgr).__exit__ #       
    value = type(mgr).__enter__(mgr)
    exc = True
    
    try:
        try:
            VAR = value #     as VAR
            BLOCK
        except:
            exc = False
            if not exit(mgr, *sys.exc_info()):
                raise
        finally:
            if exc:
                exit(mgr, None, None, None)
    

    このプロセスにはいくつかの詳細があります.
  • コンテキストマネージャに__enter()__または__exit()__のいずれかの方法がない場合、解釈器はAttributeErrorを放出する.
  • BLOCKで異常が発生した後、__exit()__メソッドがTrueとみなされる値を返すと、この異常は投げ出されず、後続のコードが実行され続けます.

  • 次に、上記のプロセスを2つの方法で実現しましょう.
    コンテキストマネージャクラスの実装
    第1の方法は、インスタンス属性dbおよびコンテキストマネージャに必要な方法__enter()__および__exit()__を含むクラスを実装することである.
    class transaction(object):
        def __init__(self, db):
            self.db = db
    
        def __enter__(self):
            self.db.begin()
    
        def __exit__(self, type, value, traceback):
            if type is None:
                db.commit()
            else:
                db.rollback()
    
    withの実行プロセスを理解すると、この実装は容易に理解できる.以下で紹介する実装方式は,その原理を理解するには複雑である.
    ジェネレータ装飾の使用
    Pythonの標準ライブラリでは、ジェネレータを使用してコンテキストマネージャを取得できるアクセサリーがあります.ジェネレータアクセラレータを使用する手順は、次のとおりです.
    from contextlib import contextmanager
    
    @contextmanager
    def transaction(db):
        db.begin()
    try:
        yield db
    except:
        db.rollback()
        raise
    else:
        db.commit()
    

    第一目で見ると、このような実現方式はもっと簡単だが、そのメカニズムはもっと複雑だ.実行プロセスを見てみましょう.
  • Python解釈器がyieldキーワードを認識すると、defは通常の関数に代わるジェネレータ関数を作成します(クラス定義以外では関数の代替方法が好きです).
  • デコレーションcontextmanagerが呼び出され、呼び出された後にGeneratorContextManagerインスタンスが生成されるヘルプメソッドが返される.最終的なwith式のEXPRは、contentmanagerデザイナによって返されるヘルプ関数を呼び出す.
  • with式はtransaction(db)を呼び出し、実際にはヘルプ関数を呼び出す.ヘルプ関数はジェネレータ関数を呼び出し、ジェネレータ関数はジェネレータを作成します.
  • ヘルプ関数は、このジェネレータをGeneratorContextManagerに渡し、GeneratorContextManagerのインスタンスオブジェクトをコンテキストマネージャとして作成します.
  • with式は、インスタンスオブジェクトのコンテキストマネージャの__enter()__メソッドを呼び出す.
  • __enter()__メソッドでは、このジェネレータのnext()メソッドが呼び出されます.このとき、ジェネレータメソッドはyield dbで停止し、dbnext()の戻り値とする.as VARがある場合、VARに割り当てられます.
  • withのうちBLOCKが実行される.
  • BLOCK実行終了後、コンテキストマネージャの__exit()__メソッドが呼び出される.__exit()__メソッドは、ジェネレータのnext()メソッドを再び呼び出す.StopIteration異常が発生した場合、pass.
  • 例外ジェネレータメソッドが発生しない場合はdb.commit()が実行され、そうでない場合はdb.rollback()が実行されます.

  • 上記の手順のコードの概要を見てみましょう.
    def contextmanager(func):
        def helper(*args, **kwargs):
            return GeneratorContextManager(func(*args, **kwargs))
        return helper
    
    class GeneratorContextManager(object):
        def __init__(self, gen):
            self.gen = gen
    
        def __enter__(self):
            try:
                return self.gen.next()
            except StopIteration:
                raise RuntimeError("generator didn't yield")
    
        def __exit__(self, type, value, traceback):
            if type is None:
                try:
                    self.gen.next()
                except StopIteration:
                    pass
                else:
                    raise RuntimeError("generator didn't stop")
            else:
                try:
                    self.gen.throw(type, value, traceback)
                    raise RuntimeError("generator didn't stop after throw()")
                except StopIteration:
                    return True
                except:
                    if sys.exc_info()[1] is not value:
                        raise
    

    まとめ
    Pythonのwith式には多くのPython特性が含まれている.時間をかけてwithを食べるのはとても価値のあることです.
    その他の例
    ロック:
    @contextmanager
    def locked(lock):
        lock.acquired()
    try:
        yield
    finally:
        lock.release()
    

    標準出力リダイレクト:
    @contextmanager
    def stdout_redirect(new_stdout):
        old_stdout = sys.stdout
        sys.stdout = new_stdout
    try:
        yield
    finally:
        sys.stdout = old_stdout
    
    with open("file.txt", "w") as f:
        with stdout_redirect(f):
            print "hello world"
    

    参照
  • The Python "with"Statement by Example
  • PEP 343
  • 元アドレス:Pythonのキーワードwith詳細-楽正