50

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

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

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()
4

5 に答える 5

24

あなたがどのような競合について質問しているのか、例の問題もよくわかりません。同じファイルに対して2つの独立したハンドルが共存していても問題ありません。

あなたの質問に答えて、ジェネレーターに新しいclose()メソッドがあることを知ったことを私は知りませんでした:

close()ジェネレーター内で新しいGeneratorExit例外を発生させて、反復を終了します。この例外を受け取った場合、ジェネレーターのコードはraiseGeneratorExitまたは。のいずれかである必要がありますStopIteration

close()ジェネレーターがガベージコレクションされるときに呼び出されるため、ジェネレーターが破棄される前に、ジェネレーターのコードが最後に実行される可能性があります。この最後のチャンスはtry...finally、ジェネレーターのステートメントが機能することを保証できることを意味します。これで、finally句は常に実行される機会が得られます。これはちょっとした言語トリビアのように見えますが、ジェネレーターを使用しており、 PEP343で説明されているステートメントtry...finallyを実装するために実際に必要です。with

http://docs.python.org/whatsnew/2.5.html#pep-342-new-generator-features

これは、withステートメントがジェネレーターで使用されているが、途中で生成されても戻らないという状況を処理し__exit__ます。ジェネレーターがガベージコレクションされると、コンテキストマネージャーのメソッドが呼び出されます。


編集

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

ロックに関する限り、RafałDowgirdは、「ジェネレーターは、リソースを保持する他のオブジェクトとまったく同じであることに注意する必要があります」と言ったときに、頭を釘にぶつけたと思います。withこの関数には同じデッドロックの問題があるため、このステートメントはここではそれほど適切ではないと思います。

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

generator = coroutine()
generator.next()
lock.acquire() # whoops!
于 2009-03-26T10:12:08.520 に答える
9

本当の争いはないと思います。ジェネレーターは、リソースを保持する他のオブジェクトとまったく同じであることに注意する必要があります。そのため、ジェネレーターが適切にファイナライズされていることを確認する (およびオブジェクトが保持するリソースとの競合/デッドロックを回避する) ことは、作成者の責任です。ここで私が目にする唯一の (マイナーな) 問題は、ジェネレーターがコンテキスト管理プロトコルを実装していないことです (少なくとも Python 2.5 の時点では)。

with coroutine() as cr:
  doSomething(cr)

代わりに、次のことを行う必要があります。

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

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

于 2009-03-26T10:44:15.073 に答える
1

任意のコードを実行できるためyield、yield ステートメントに対してロックを保持することには非常に注意が必要です。ただし、オーバーライドまたは変更された可能性のあるメソッドまたは関数を呼び出すなど、他の多くの方法で同様の効果を得ることができます。

close()ただし、ジェネレーターは、明示的な呼び出しを使用するか、単にガベージ コレクションを実行することによって、常に (ほぼ常に) "閉じられています" 。ジェネレーターを閉じると、ジェネレーター内で例外がスローされるため、finally 句が実行され、ステートメントのクリーンアップなどが行われます。例外をキャッチすることはできますが、yield ではなくGeneratorExit、関数をスローまたは終了する (つまり、例外をスローする) 必要があります。StopIterationあなたが書いたようなケースでガベージ コレクターに頼ってジェネレーターを閉じるのはおそらくお粗末な方法です。 .

于 2009-03-26T10:44:54.543 に答える
1

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そのリソースを解放しますか?

    これは、ブロックが完了した直後に 1 回行われます前者は、何度か発生する可能性があるため、中に解放されないことを意味します。後者は、完了後に解放することを意味します。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
于 2018-01-19T15:03:36.820 に答える
0

それが私が物事がうまくいくと期待した方法です。はい、ブロックは完了するまでリソースを解放しません。そのため、その意味で、リソースは字句の入れ子から逃れています。ただし、これはwithブロック内で同じリソースを使用しようとした関数呼び出しを行うことと同じです。何らかの理由でブロックがまだ終了していない場合は何も役に立ちません。ジェネレーターに固有のものではありません。

ただし、心配する価値のあることの1つは、ジェネレーターが再開されない場合の動作です。withブロックがブロックのように機能し、終了時にパーツfinallyを呼び出すことを期待して__exit__いましたが、そうではないようです。

于 2009-03-26T10:19:42.963 に答える