私はグーグルで検索しました 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
これは良い/悪い習慣ですか?どうして?
注:この回答は、基になる___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
を返す)かを決定する必要があります。管理対象オブジェクトはどちらの方法でもそれを実行しようとする可能性があります。ラッパーオブジェクトにとって何が意味があるかを判断する必要があります。
あなたの最初の例は良い考えではありません:
slave_connection.__enter__
が例外をスローした場合はどうなりますか?
master_connection
はそのリソースを取得しますslave_connection
が失敗するDataSync.__enter__
は例外を伝播しますDataSync.__exit__
は実行されませんmaster_connection
はクリーンアップされません!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)
同じ質問をする:
slave_connection.__enter__
が例外をスローした場合はどうなりますか?
stack
がmaster_connection
をクリーンアップします。master_connection.__exit__
が例外をスローするとどうなりますか?
slave_connection
がクリーンアップされますOK、slave_connection.__exit__
が例外をスローした場合はどうなりますか?
ExitStack
は、スレーブ接続に何が起こってもmaster_connection.__exit__
を呼び出すようにします__enter__
を直接呼び出すことには何の問題もありませんが、複数のオブジェクトで呼び出す必要がある場合は、適切にクリーンアップするようにしてください。
ヘッダーのみに応答します。つまり、__enter__
と__exit__
を呼び出します。 IPythonプロンプトから。あなたはこのようにそれを行うことができます:
ctx.__enter__()
ctx.__exit__(*sys.exc_info())