web-dev-qa-db-ja.com

__enter__と__exit__を手動で呼び出す

私はグーグルで検索しました calling __enter__ manually しかし、運がありません。それで、データベースに接続/切断するために__enter__および__exit__関数(元々はwithステートメントで使用された)を使用するMySQLコネクタクラスがあると想像してみましょう。

そして、これらの接続のうち2つを使用するクラスを作成しましょう(たとえば、データ同期のために)。 注:これは私の実際のシナリオではありませんが、最も単純な例のようです

すべてを連携させる最も簡単な方法は、次のようなクラスです。

class DataSync(object):

    def __init__(self):
        self.master_connection = MySQLConnection(param_set_1)
        self.slave_connection = MySQLConnection(param_set_2)

    def __enter__(self):
            self.master_connection.__enter__()
            self.slave_connection.__enter__()
            return self

    def __exit__(self, exc_type, exc, traceback):
            self.master_connection.__exit__(exc_type, exc, traceback)
            self.slave_connection.__exit__(exc_type, exc, traceback)

    # Some real operation functions

# Simple usage example
with DataSync() as sync:
    records = sync.master_connection.fetch_records()
    sync.slave_connection.Push_records(records)

[〜#〜] q [〜#〜]:このように手動で__enter__/__exit__を呼び出しても大丈夫ですか(何か問題がありますか)?

Pylint 1.1.0はこれについて警告を発しませんでしたし、それについての記事も見つかりませんでした(最初のグーグルリンク)。

そして、電話はどうですか?

try:
    # Db query
except MySQL.ServerDisconnectedException:
    self.master_connection.__exit__(None, None, None)
    self.master_connection.__enter__()
    # Retry

これは良い/悪い習慣ですか?どうして?

23
Vyktor

注:この回答は、基になる___enter___および___exit___メソッドへの複数の呼び出しがある場合に発生する可能性のある障害を適切に考慮していません。それを扱っているものについては、エリックの答えを参照してください。

いいえ、問題はありません。標準ライブラリにはそれを行う場所さえあります。 multiprocessing module のように:

_class SemLock(object):

    def __init__(self, kind, value, maxvalue, *, ctx):
            ...
            try:
                sl = self._semlock = _multiprocessing.SemLock(
                    kind, value, maxvalue, self._make_name(),
                    unlink_now)
            except FileExistsError:
                pass
    ...

    def __enter__(self):
        return self._semlock.__enter__()

    def __exit__(self, *args):
        return self._semlock.__exit__(*args)
_

または tempfile module

_class _TemporaryFileWrapper:

    def __init__(self, file, name, delete=True):
        self.file = file
        self.name = name
        self.delete = delete
        self._closer = _TemporaryFileCloser(file, name, delete)

    ...

    # The underlying __enter__ method returns the wrong object
    # (self.file) so override it to return the wrapper
    def __enter__(self):
        self.file.__enter__()
        return self

    # Need to trap __exit__ as well to ensure the file gets
    # deleted when used in a with statement
    def __exit__(self, exc, value, tb):
        result = self.file.__exit__(exc, value, tb)
        self.close()
        return result
_

標準ライブラリの例では、2つのオブジェクトに対して___enter___/___exit___を呼び出していませんが、1つではなく複数のオブジェクトのコンテキストを作成/破棄するオブジェクトがある場合は、___enter___/___exit___はすべて問題ありません。

唯一の潜在的な落とし穴は、管理しているオブジェクトの___enter___ ___exit___呼び出しの戻り値を適切に処理することです。 ___enter___では、ラッパーオブジェクトのユーザーが_with ... as <state>:_呼び出しから戻るために必要なstateを返すようにする必要があります。 ___exit___を使用すると、コンテキスト内で発生した例外を伝播するか(Falseを返す)、それを抑制する(Trueを返す)かを決定する必要があります。管理対象オブジェクトはどちらの方法でもそれを実行しようとする可能性があります。ラッパーオブジェクトにとって何が意味があるかを判断する必要があります。

13
dano

あなたの最初の例は良い考えではありません:

  1. slave_connection.__enter__が例外をスローした場合はどうなりますか?

    • master_connectionはそのリソースを取得します
    • slave_connectionが失敗する
    • DataSync.__enter__は例外を伝播します
    • DataSync.__exit__は実行されません
    • master_connectionはクリーンアップされません!
    • 悪いことの可能性
  2. master_connection.__exit__が例外をスローするとどうなりますか?

    • DataSync.__exit__早期終了
    • slave_connectionはクリーンアップされません!
    • 悪いことの可能性

contextlib.ExitStackはここで役立ちます:

def __enter__(self):
    with ExitStack() as stack:
        stack.enter_context(self.master_connection)
        stack.enter_context(self.slave_connection)
        self._stack = stack.pop_all()
    return self

def __exit__(self, exc_type, exc, traceback):
    self._stack.__exit__(self, exc_type, exc, traceback)

同じ質問をする:

  1. slave_connection.__enter__が例外をスローした場合はどうなりますか?

    • Withブロックが終了し、stackmaster_connectionをクリーンアップします。
    • 大丈夫です!
  2. master_connection.__exit__が例外をスローするとどうなりますか?

    • 関係ありません、これが呼び出される前にslave_connectionがクリーンアップされます
    • 大丈夫です!
  3. OK、slave_connection.__exit__が例外をスローした場合はどうなりますか?

    • ExitStackは、スレーブ接続に何が起こってもmaster_connection.__exit__を呼び出すようにします
    • 大丈夫です!

__enter__を直接呼び出すことには何の問題もありませんが、複数のオブジェクトで呼び出す必要がある場合は、適切にクリーンアップするようにしてください。

14
Eric

ヘッダーのみに応答します。つまり、__enter____exit__を呼び出します。 IPythonプロンプトから。あなたはこのようにそれを行うことができます:


ctx.__enter__()
ctx.__exit__(*sys.exc_info())

0
Roland Puntaier