サーチ…


前書き

Pythonのコンテキストマネージャは広く使用されていますが、その使用目的の背後にある目的を理解する人はほとんどいません。ファイルの読み書きによく使用されるこれらのステートメントは、システムメモリを節約し、特定のリソースが特定のプロセスでのみ使用されていることを保証することによってリソース管理を向上させます。このトピックでは、Pythonのコンテキストマネージャーの使い方を説明しています。

構文

  • "context_manager"( "エイリアス"として)(、 "context_manager"( "エイリアス"として)?)*:

備考

コンテキストマネージャは、 PEP 343で定義されています。それらは、リソース管理のためのより簡潔なメカニズムとして、 try ... finally構築するtry ... finallyよりも使用されることが意図されています。正式な定義は以下の通りです。

このPEPでは、コンテキストマネージャは、 __enter__()および__exit__()メソッドを提供しています。 __enter__()メソッドは、with文の本体への__exit__()時に呼び出されます。

次に、 withステートメントを次のように定義します。

with EXPR as VAR:
    BLOCK

上記の声明の翻訳は:

   mgr = (EXPR)
   exit = type(mgr).__exit__  # Not calling it yet
   value = type(mgr).__enter__(mgr)
   exc = True
   try:
       try:
           VAR = value  # Only if "as VAR" is present
           BLOCK
       except:
           # The exceptional case is handled here
           exc = False
           if not exit(mgr, *sys.exc_info()):
               raise
           # The exception is swallowed if exit() returns true
   finally:
       # The normal and non-local-goto cases are handled here
       if exc:
           exit(mgr, None, None, None)

コンテキストマネージャーとwithステートメント

コンテキストマネージャは、コンテキスト(コードブロック)の開始時終了時に通知されるオブジェクトです。通常、 withステートメントで1つを使用します。それは通知の世話をします。

たとえば、ファイルオブジェクトはコンテキストマネージャです。コンテキストが終了すると、ファイルオブジェクトは自動的に閉じられます。

open_file = open(filename)
with open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

上記の例は、通常、 asキーワードを使用して簡略化されていas

with open(filename) as open_file:
    file_contents = open_file.read()

# the open_file object has automatically been closed.

ブロックの実行が終了すると、コンテキストマネージャのexitメソッドが呼び出されます。これには例外が含まれており、エラーにより開いているファイルや接続が途中で終了する場合に役立ちます。ファイルや接続を適切に閉じないでスクリプトを終了すると、データが失われるなどの問題が発生する可能性があります。コンテキストマネージャを使用することにより、このような方法で損害や損失を防ぐための予防措置が常に講じられるようにすることができます。この機能はPython 2.5で追加されました。

ターゲットに割り当てる

多くのコンテキストマネージャは、入力時にオブジェクトを返します。そのオブジェクトをwithステートメントの新しい名前に割り当てることができます。

たとえば、 withステートメントでデータベース接続を使用すると、カーソルオブジェクトが得られます。

with database_connection as cursor:
    cursor.execute(sql_query)

ファイルオブジェクトはそれ自身を返します。これにより、ファイルオブジェクトを開いて、1つの式でコンテキストマネージャとして使用することができます。

with open(filename) as open_file:
    file_contents = open_file.read()

独自のコンテキストマネージャを書く

コンテキストマネージャは、 __enter__()__exit__() 2つのマジックメソッドを実装するオブジェクトです(他のメソッドも実装できます)。

class AContextManager():

    def __enter__(self):
        print("Entered")
        # optionally return an object
        return "A-instance"

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exited" + (" (with an exception)" if exc_type else ""))
        # return True if you want to suppress the exception

コンテキストが例外で終了した場合、その例外に関する情報はtriple exc_typeexc_valuetraceback (これらはsys.exc_info()関数によって返されるものと同じ変数です)としてsys.exc_info()れます。コンテキストが正常に終了する場合、これらの引数の3つはすべてNoneます。

例外が発生し、 __exit__メソッドに渡された場合、このメソッドは例外を抑制するためにTrueを返すことができます。そうしないと、例外は__exit__関数の最後に再発生します。

with AContextManager() as a:
    print("a is %r" % a)
# Entered
# a is 'A-instance'
# Exited

with AContextManager() as a:
    print("a is %d" % a)
# Entered
# Exited (with an exception)
# Traceback (most recent call last):
#   File "<stdin>", line 2, in <module>
# TypeError: %d format: a number is required, not str

2番目の例では、with-statement本体の途中で例外が発生しても、例外が外部スコープに伝播する前に__exit__ハンドラがまだ実行されていることに注意してください。

__exit__メソッドだけが必要な場合は、コンテキストマネージャのインスタンスを返すことができます:

class MyContextManager:
    def __enter__(self):
        return self

    def __exit__(self):
        print('something')

ジェネレータ構文を使用して独自のコンテキストマネージャを作成する

contextlib.contextmanagerデコレータのおかげで、ジェネレータ構文を使ってコンテキストマネージャを書くこともできます:

import contextlib

@contextlib.contextmanager
def context_manager(num):
    print('Enter')
    yield num + 1
    print('Exit')

with context_manager(2) as cm:
    # the following instructions are run when the 'yield' point of the context
    # manager is reached.
    # 'cm' will have the value that was yielded
    print('Right in the middle with cm = {}'.format(cm))

次を生成する:

Enter
Right in the middle with cm = 3
Exit

デコレータは、ジェネレータを1に変換することによってコンテキストマネージャを作成する作業を簡素化します。 yield式の前のすべてが__enter__メソッドになり、 __enter__された値はジェネレータによって返された値になります(withステートメントの変数にバインドできます) __exit__式の後のすべてが__exit__メソッドになります。

例外は、コンテキストマネージャによって処理する必要がある場合、 try..except..finally -blockは、発電機で書くことができ、中に発生したすべての例外with -ブロックは、この例外ブロックによって処理されます。

@contextlib.contextmanager
def error_handling_context_manager(num):
    print("Enter")
    try:
        yield num + 1
    except ZeroDivisionError:
        print("Caught error")
    finally:
        print("Cleaning up")
    print("Exit")

with error_handling_context_manager(-1) as cm:
    print("Dividing by cm = {}".format(cm))
    print(2 / cm)

これにより、

Enter
Dividing by cm = 0
Caught error
Cleaning up
Exit

複数のコンテキストマネージャ

複数のコンテンツマネージャを同時に開くことができます。

with open(input_path) as input_file, open(output_path, 'w') as output_file:

    # do something with both files. 
    
    # e.g. copy the contents of input_file into output_file
    for line in input_file:
        output_file.write(line + '\n')

コンテキストマネージャのネストと同じ効果があります。

with open(input_path) as input_file:
    with open(output_path, 'w') as output_file:
        for line in input_file:
            output_file.write(line + '\n')

リソースの管理

class File():
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.open_file = open(self.filename, self.mode)
        return self.open_file

    def __exit__(self, *args):
        self.open_file.close()

__init__()メソッドはオブジェクトを設定します。この場合、ファイル名とモードをオープンファイルに設定します。 __enter__()が開き、ファイルと返す__exit__()だけで、それをクローズします。

これらのマジックメソッド( __enter____exit__ )を使うwith 、withステートメントで簡単に使用できるオブジェクトを実装できます。

ファイルクラスを使用する:

for _ in range(10000):
    with File('foo.txt', 'w') as f:
        f.write('foo')


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow