18

私はMPIベースのアプリケーションを作成しています(ただし、MPIは私の質問では関係ありません。理由を明らかにするためだけに言及します)。場合によっては、プロセスよりも作業項目が少ない場合は、新しいコミュニケーターを作成する必要があります。何の関係もないプロセスを除いて。最後に、新しいコミュニケーターは、実行する必要のあるプロセスによって(そしてそれらによってのみ)解放される必要があります。

それを行うためのきちんとした方法は、次のように書くことです。

with filter_comm(comm, nworkitems) as newcomm:
    ... do work with communicator newcomm...

実行する作業があるプロセスによってのみ実行される本体。

コンテキストマネージャーに本文の実行を回避する方法はありますか?コンテキストマネージャーは制御フローを隠さないように設計されていることは理解していますが、私の場合は明確にするために正当化されると思うので、それを回避できるかどうか疑問に思います。

4

3 に答える 3

16

コンテキストマネージャー本体を条件付きでスキップする機能が提案されていますが、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)
于 2015-02-17T22:19:26.053 に答える
6

この機能は拒否されたようです。Python開発者は、多くの場合、明示的なバリアントを好みます。

if need_more_workers():
    newcomm = get_new_comm(comm)
    # ...

高階関数を使用することもできます。

def filter_comm(comm, nworkitems, callback):
    if foo:
        callback(get_new_comm())

# ...

some_local_var = 5
def do_work_with_newcomm(newcomm):
    # we can access the local scope here

filter_comm(comm, nworkitems, do_work_with_newcomm)
于 2012-05-04T11:29:57.133 に答える
1

代わりにこのようなものはどうですか?

@filter_comm(comm, nworkitems)
def _(newcomm):  # Name is unimportant - we'll never reference this by name.
    ... do work with communicator newcomm...

デコレータを実装して、必要filter_commな作業を実行し、それらの結果に基づいて、ラップアラウンドされた関数を実行するかどうかを決定し、を渡します。commnworkitemsnewcomm

ほどエレガントでwithはありませんが、他の提案よりも少し読みやすく、欲しいものに近いと思います。_内部関数には、その名前が気に入らない場合以外の名前を付けることもできますが、文法で実際には使用しない名前が必要な場合にPythonで使用される通常の名前であるため、この名前を使用しました。

于 2015-07-21T17:37:09.983 に答える