21

Pythonでは、ジェネレーター内でwithステートメントを使用する必要がありますか? 明確にするために、デコレータを使用してジェネレータ関数からコンテキスト マネージャを作成することについて質問しているわけではありません。withステートメントをジェネレーター内のコンテキストマネージャーとして使用すると、少なくともいくつかのケースで例外StopIterationが発生するため、固有の問題があるかどうかを尋ねています。GeneratorExit以下に 2 つの例を示します。

この問題の良い例は、Beazley の例 (106 ページ) で取り上げられています。with ステートメントを使用するように変更して、opener メソッドの yield 後にファイルが明示的に閉じられるようにしました。また、結果の反復中に例外をスローする 2 つの方法も追加しました。

import os
import fnmatch

def find_files(topdir, pattern):
    for path, dirname, filelist in os.walk(topdir):
        for name in filelist:
            if fnmatch.fnmatch(name, pattern):
                yield os.path.join(path,name)
def opener(filenames):
    f = None
    for name in filenames:
        print "F before open: '%s'" % f
        #f = open(name,'r')
        with open(name,'r') as f:
            print "Fname: %s, F#: %d" % (name, f.fileno())
            yield f
            print "F after yield: '%s'" % f
def cat(filelist):
    for i,f in enumerate(filelist):
        if i ==20:
            # Cause and exception
            f.write('foobar')
        for line in f:
            yield line
def grep(pattern,lines):
    for line in lines:
        if pattern in line:
            yield line

pylogs = find_files("/var/log","*.log*")
files = opener(pylogs)
lines = cat(files)
pylines = grep("python", lines)
i = 0
for line in pylines:
    i +=1
    if i == 10:
        raise RuntimeError("You're hosed!")

print 'Counted %d lines\n' % i

この例では、コンテキスト マネージャーは opener 関数でファイルを正常に閉じます。例外が発生すると、例外からのトレースが表示されますが、ジェネレーターは黙って停止します。with ステートメントが例外をキャッチした場合、なぜジェネレーターは続行しないのでしょうか?

ジェネレーター内で使用する独自のコンテキスト マネージャーを定義するとき。を無視したという実行時エラーが表示されますGeneratorExit。例えば:

class CManager(object):  
    def __enter__(self):
          print "  __enter__"
          return self
    def __exit__(self, exctype, value, tb):
        print "  __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
        return True

def foo(n):
    for i in xrange(n):
        with CManager() as cman:
            cman.val = i
            yield cman
# Case1 
for item in foo(10):
    print 'Pass - val: %d' % item.val
# Case2
for item in foo(10):
    print 'Fail - val: %d' % item.val
    item.not_an_attribute

この小さなデモは、ケース 1 では例外が発生せずに正常に動作しますが、属性エラーが発生するケース 2 では失敗します。ここではRuntimeException、 with ステートメントがGeneratorExit例外をキャッチして無視したために発生したことがわかります。

このトリッキーなユースケースのルールを明確にするのを手伝ってくれる人はいますか? 私はそれが私がしていること、または私の__exit__方法でしていないことだと思います。re-raise にコードを追加しようとしましGeneratorExitたが、それは役に立ちませんでした。

4

2 に答える 2

10

データ モデル エントリからobject.__exit__

例外が提供され、メソッドが例外を抑制したい場合 (つまり、例外が伝播されないようにしたい場合)、真の値を返す必要があります。それ以外の場合、例外はこのメソッドの終了時に通常どおり処理されます。

あなたの関数では、すべての例外を抑制するものを__exit__返しています。return に変更すると、例外は通常どおり発生し続けます (唯一の違いは、関数が呼び出されることを保証し、自分で確実にクリーンアップできることです)。TrueFalse__exit__

たとえば、コードを次のように変更します。

def __exit__(self, exctype, value, tb):
    print "  __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
    if exctype is GeneratorExit:
        return False
    return True

を抑制せずに正しいことを行うことができますGeneratorExit。これで、属性エラーのみが表示されます。おそらく経験則は例外処理と同じであるべきです -処理方法を知っている場合にのみ例外をインターセプトしてください__exit__リターンを持つことTrueは、次の点を除いて、ベアを持つことよりも同等です (おそらくわずかに悪いです!):

try:
   something()
except: #Uh-Oh
   pass

が発生するAttributeErrorと(キャッチされない場合)、ジェネレーターオブジェクトの参照カウントが0に低下しGeneratorExit、ジェネレーター内で例外がトリガーされて、それ自体をクリーンアップできるようになることに注意してください。my を使用して__exit__、次の 2 つのケースを試してみると、私の言いたいことがわかると思います。

try:
    for item in foo(10):
        print 'Fail - val: %d' % item.val
        item.not_an_attribute
except AttributeError:
    pass

print "Here"  #No reference to the generator left.  
              #Should see __exit__ before "Here"

g = foo(10)
try:
    for item in g:
        print 'Fail - val: %d' % item.val
        item.not_an_attribute
except AttributeError:
    pass

print "Here"
b = g  #keep a reference to prevent the reference counter from cleaning this up.
       #Now we see __exit__ *after* "Here"
于 2013-03-12T04:37:40.220 に答える
1
class CManager(object):
    def __enter__(self):
          print "  __enter__"
          return self
    def __exit__(self, exctype, value, tb):
        print "  __exit__; excptype: '%s'; value: '%s'" % (exctype, value)
        if exctype is None:
            return

        # only re-raise if it's *not* the exception that was
        # passed to throw(), because __exit__() must not raise
        # an exception unless __exit__() itself failed.  But throw()
        # has to raise the exception to signal propagation, so this
        # fixes the impedance mismatch between the throw() protocol
        # and the __exit__() protocol.
        #
        if sys.exc_info()[1] is not (value or exctype()):
            raise 
于 2013-03-12T04:51:21.097 に答える