Python の「with」ブロック内から yield するのは安全ですか (その理由も)?

StackOverflow https://stackoverflow.com/questions/685046

  •  22-08-2019
  •  | 
  •  

質問

コルーチンとリソース取得を組み合わせると、意図しない (または直感的ではない) 結果が生じる可能性があるようです。

基本的な問題は、次のようなものが機能するかどうかです。

def coroutine():
    with open(path, 'r') as fh:
        for line in fh:
            yield line

それはそうなります。(テストしてみることもできますよ!)

さらに深い懸念は、 with の代替となるものであるはずです finally, 、ブロックの最後にリソースが解放されるようにします。コルーチンは実行を一時停止および再開できます。 内でwith ブロックしますので、 紛争はどのように解決されますか?

たとえば、コルーチンがまだ返されていないときに、コルーチンの内部と外部の両方でファイルを読み取り/書き込みで開くと、次のようになります。

def coroutine():
    with open('test.txt', 'rw+') as fh:
        for line in fh:
            yield line

a = coroutine()
assert a.next() # Open the filehandle inside the coroutine first.
with open('test.txt', 'rw+') as fh: # Then open it outside.
    for line in fh:
        print 'Outside coroutine: %r' % repr(line)
assert a.next() # Can we still use it?

アップデート

前の例では書き込みロックされたファイル ハンドルの競合を想定していましたが、ほとんどの OS はプロセスごとにファイル ハンドルを割り当てるため、競合は発生しません。(例があまり意味をなさないことを指摘してくれた @Miles に敬意を表します。) これが実際のデッドロック状態を示す、私が修正した例です。

import threading

lock = threading.Lock()

def coroutine():
    with lock:
        yield 'spam'
        yield 'eggs'

generator = coroutine()
assert generator.next()
with lock: # Deadlock!
    print 'Outside the coroutine got the lock'
assert generator.next()
役に立ちましたか?

解決

私は本当にあなたがについて尋ねているのか葛藤を理解し、また例の問題点はありません:それは、同じファイルへの2つの共存、独立したハンドルを持って大丈夫です。

一つのことは私が発電機に新しいclose()メソッドがあることをあなたの質問、それに応じて、学んだことを知りませんでした。

  

close()は、反復を終了するために発電機内部の新しいGeneratorExit例外を発生させます。この例外を受信すると、発電機のコードのいずれかGeneratorExitまたはStopIterationを上げる必要があります。

     

close()は発電機がガベージコレクトされたときに呼び出されるので、これは発電機のコードは、発電機が破壊される前に実行する最後のチャンスを得る手段です。この最後のチャンスは、発電機でtry...finallyの文は、現在の動作が保証できることを意味します。 finally句は現在、常に実行する機会を得るでしょう。これは、言語トリビアのマイナービットのように思えるが、使用して発電機やtry...finallyはPEP 343で記述withステートメントを実行するために実際に必要である。

     

http://docs.python.org/新着情報/ 2.5.html#PEP-342-新発電機能する

それはwith文を発電機に使用されている状況を処理し、それが途中で生じませんが、決して返し、発電機がガベージコレクトされるとき、コンテキストマネージャの__exit__メソッドが呼び出されますので。

<時間>

編集

ファイルハンドルの問題に関して:私は時々、POSIXに似ていないプラットフォームが存在することを忘れています。 :)

限りロックが行くように、私は彼が言うときラファウDowgird爪の上に頭を打つと思う「あなただけの発電機だけでリソースを保持している他のオブジェクトと同様であることを認識する必要があります。」この機能は、同じデッドロックの問題に苦しんでいるので、私は、with文はここで本当に関連しているとは思わない:

def coroutine():
    lock.acquire()
    yield 'spam'
    yield 'eggs'
    lock.release()

generator = coroutine()
generator.next()
lock.acquire() # whoops!

他のヒント

私は本当の競合があるとは思いません。あなただけの発電機がちょうどリソースを保持している他のオブジェクトと同様であることを認識する必要があり、それが適切に確定されていることを確認するために(およびオブジェクトが保持しているリソースとの競合/デッドロックを回避するために)作成者の責任です。私はここを参照してくださいのみ(マイナー)の問題は、発電機は、(少なくともPythonの2.5のような)コンテキスト管理プロトコルを実装していないということですので、あなただけではなくできます:

with coroutine() as cr:
  doSomething(cr)

代わりに持っています:

cr = coroutine()
try:
  doSomething(cr)
finally:
  cr.close()

ガベージコレクタはとにかくclose()を行いますが、それはリソースを解放するためにそれに頼るのは悪い習慣です。

yieldが任意のコードを実行することができますので、私はyield文の上にロックを保持しているのは非常に警戒すると思います。あなたは上書きあるいは変更されているかもしれないメソッドや関数を呼び出すなどの、しかし、他の多くの方法で同様の効果を得ることができます。

ジェネレータ、しかし、常に(ほぼ常に)「閉」、明示的なclose()呼び出しで、または単にガベージコレクションであることによってです。発電機を閉じると、発電機内部のGeneratorExit例外がスローされますので、finally節を実行し、などの文のクリーンアップ、とあなたは、例外をキャッチすることができますが、むしろ降伏よりも、(すなわちStopIteration例外をスロー)機能を投げるか、終了する必要があります。それはあなたが書いたようにあなたが望むかもしれないよりそれが後に起こる可能性があるため、場合によっては発電機を閉じるためにガベージコレクタに依存するため、おそらく貧しい練習だし、誰かがsys._exit()を呼び出した場合、その後、あなたのクリーンアップがまったく起こらない可能性がありますます。

それは私が仕事に物事を期待どのようになります。はい、それが完了するまでブロックは、そのリソースを解放しないので、その意味では、リソースは、それが字句営巣だ脱出しました。しかし、これはブロックと内の同じリソースを使用しようとした関数呼び出しを行うことに違いはありません - 何もブロックが、のないのまだのどんな<ために、終了した場合には、あなたを助けていません/ em>の理由。それは実際に発電機に固有のものではありません。

行動ですが、気にせ価値があるかもしれないことの一つは、発電機がある場合は、のことはありませんのを再開しました。私はwithブロックのように動作し、終了時にfinallyの一部を呼び出すために__exit__ブロックを期待しているだろうが、それはケースではないようです。

TLDR の場合は、次のように見てください。

with Context():
    yield 1
    pass  # explicitly do nothing *after* yield
# exit context after explicitly doing nothing

Context 後に終了 pass 完了しました(つまり、何もない)、 pass 後に実行される yield 完了しました(つまり、実行が再開されます)。それで、 with 終わる 制御が再開されるのは yield.

TLDR:あ with コンテキストは次の場合に保持されたままになります yield コントロールを解放します。


実際、ここで関連するルールは 2 つだけです。

  1. いつ with リソースを解放しますか?

    そうなります 一度 そして直接的に そのブロックは完了しました。前者はリリースしないことを意味します その間 ある yield, 、それは何度か起こる可能性があるためです。後者はリリースすることを意味します yield 完了しました。

  2. いつ yield 完了?

    のことを考える yield 逆呼び出しとして:制御は呼び出された側ではなく、呼び出し側に渡されます。同様に、 yield 呼び出しが制御を返すときと同様に、制御が返されると完了します。

どちらも注意してください with そして yield ここでは意図したとおりに機能しています!のポイント with lock リソースを保護することであり、リソースは保護され続けます。 yield. 。この保護はいつでも明示的に解除できます。

def safe_generator():
  while True:
    with lock():
      # keep lock for critical operation
      result = protected_operation()
    # release lock before releasing control
    yield result
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top