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
たが、それは役に立ちませんでした。