コンテキストマネージャー本体を条件付きでスキップする機能が提案されていますが、PEP377に記載されているように拒否されています。
私は代替案についていくつかの調査を行いました。これが私の発見です。
まず、コード例の背景について説明します。使用したいデバイスがたくさんあります。すべてのデバイスについて、デバイスのドライバーを取得する必要があります。次に、ドライバーを使用してデバイスを操作します。最後にドライバーを解放して、他の人がドライバーを取得してデバイスを操作できるようにします。
ここでは異常なことは何もありません。コードはおおよそ次のようになります。
driver = getdriver(devicename)
try:
dowork(driver)
finally:
releasedriver(driver)
しかし、惑星が正しく位置合わせされていない満月ごとに、デバイス用に取得したドライバーは不良であり、デバイスで作業を行うことはできません。これは大したことではありません。このラウンドでデバイスをスキップして、次のラウンドで再試行してください。通常、ドライバーはその時は良いです。ただし、不良ドライバーでもリリースする必要があります。そうしないと、新しいドライバーを取得できません。
(ファームウェアはプロプライエタリであり、ベンダーはこのバグを修正したり、認識したりすることを躊躇しています)
コードは次のようになります。
driver = getdriver(devicename)
try:
if isgooddriver(driver):
dowork(driver)
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
これは、デバイスで作業を行う必要があるたびに繰り返す必要のある多くの定型コードです。ステートメントと呼ばれるPythonのコンテキストマネージャーの最有力候補。次のようになります。
# note: this code example does not work
@contextlib.contextmanager
def contextgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
そして、デバイスを操作するときのコードは短くて甘いです:
# note: this code example does not work
with contextgetdriver(devicename) as driver:
dowork(driver)
しかし、これは機能しません。コンテキストマネージャーは譲歩しなければならないからです。降伏しない場合があります。譲歩しないと、RuntimeException
によってレイズされcontextmanager
ます。
したがって、コンテキストマネージャーからチェックを引き出す必要があります
@contextlib.contextmanager
def contextgetdriver(devicename):
driver = getdriver(devicename)
try:
yield driver
finally:
release(driver)
with
それをステートメントの本文に入れます
with contextgetdriver(devicename) as driver:
if isgooddriver(driver):
dowork(driver)
else:
handledrivererror(geterrordetails(driver))
これは醜いです。なぜなら、デバイスを操作するたびに繰り返す必要のある定型文が再びあるからです。
したがって、本体を条件付きで実行できるコンテキストマネージャーが必要です。しかし、PEP 377(まさにこの機能を提案)が拒否されたため、何もありません。
譲歩しない代わりに、自分で例外を発生させることができます。
@contextlib.contextmanager
def contextexceptgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
raise NoGoodDriverException(geterrordetails(driver))
finally:
release(driver)
しかし今、あなたは例外を処理する必要があります:
try:
with contextexceptgetdriver(devicename) as driver:
dowork(driver)
except NoGoodDriverException as e:
handledrivererror(e.errordetails)
これは、上記の優れたドライバーの明示的なテストとほぼ同じコードの複雑さのコストがかかります。
違い:例外を除いて、ここでは処理せず、代わりに呼び出しスタックをバブルアップして他の場所で処理することを決定できます。
また、違い:例外を処理するまでに、ドライバーはすでにリリースされています。明示的なチェックを行っている間、ドライバーはリリースされていません。(例外はwithステートメントの外側にあり、elseはwithステートメントの内側にあります)
ジェネレーターを悪用することは、本文をスキップできるコンテキストマネージャーの代わりとして非常にうまく機能することがわかりました
def generatorgetdriver(devicename):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
yield driver
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
しかし、呼び出し元のコードはループのように見えます
for driver in generatorgetdriver(devicename):
dowork(driver)
あなたがこれと一緒に暮らすことができるなら(そうしないでください)、あなたは条件付きで本体を実行することができるコンテキストマネージャーを持っています。
ボイラープレートコードを防ぐ唯一の方法は、コールバックを使用することです。
def workwithdevice(devicename, callback):
driver = getdriver(devicename)
try:
if isgooddriver(driver):
callback(driver)
else:
handledrivererror(geterrordetails(driver))
finally:
release(driver)
そして、呼び出しコード
workwithdevice(devicename, dowork)