Python では、ジェネレーター内で with ステートメントを使用する必要がありますか? 明確にするために、デコレータを使用してジェネレーター関数からコンテキスト マネージャーを作成することについて質問しているわけではありません。少なくともいくつかのケースでは例外StopIteration
をキャッチするため、ジェネレーター内でコンテキスト マネージャーとして with ステートメントを使用すると固有の問題があるかどうかを尋ねていますGeneratorExit
。次に 2 つの例を示します。
この問題の良い例は、Beazley の例 (106 ページ) に示されています。私は、opener メソッドの yield の後にファイルが明示的に閉じられるように、with ステートメントを使用するように変更しました。また、結果を反復処理するときに例外をスローできる 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
この小さなデモは、case1 では例外が発生しずに正常に動作しますが、case2 では属性エラーが発生して失敗します。ここでは、RuntimeException
with ステートメントが例外をキャッチして無視したために、例外が発生していることがわかりますGeneratorExit
。
この難しいユースケースのルールを明確にするのを手伝ってくれる人はいませんか? これは私がやっていること、または私の__exit__
方法でやっていないことだと思います。 re-raise するコードを追加してみましたGeneratorExit
が、役に立ちませんでした。
ベストアンサー1
例外が提供され、メソッドが例外を抑制したい場合 (つまり、例外が伝播されないようにしたい場合)、 true 値を返す必要があります。 それ以外の場合、このメソッドの終了時に例外は通常どおり処理されます。
関数内では、抑制する__exit__
戻り値を返しますTrue
全て例外。 return に変更するとFalse
、例外は通常どおり引き続き発生します (唯一の違いは、__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"