Как использовать диспетчер контекста python внутри генератора
В python должны использоваться внутрисегменты внутри генератора? Чтобы быть ясным, я не прошу об использовании декоратора для создания диспетчера контекста из функции генератора. Я спрашиваю, есть ли неотъемлемая проблема, используя оператор-оператор в качестве менеджера контекста внутри генератора, поскольку он будет ловить StopIteration
и GeneratorExit
исключения, по крайней мере, в некоторых случаях. Ниже приводятся два примера.
Хороший пример проблемы поднят примером Безли (стр. 106). Я изменил его, чтобы использовать оператор with, чтобы файлы были явно закрыты после выхода в методе openener. Я также добавил два способа исключения исключения при повторении результатов.
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
В этом примере менеджер контекста успешно закрывает файлы в функции открывания. Когда возникает исключение, я вижу след обратно из исключения, но генератор останавливается молча. Если оператор with-catch обнаруживает исключение, почему генератор не продолжается?
Когда я определяю свои собственные контекстные менеджеры для использования внутри генератора. Я получаю ошибки времени выполнения, говоря, что я проигнорировал 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 без каких-либо исключений, но не работает в случае2, где возникает ошибка атрибута. Здесь я вижу a RuntimeException
, потому что оператор with поймал и проигнорировал исключение GeneratorExit
.
Может кто-нибудь помочь прояснить правила для этого сложного варианта использования? Я подозреваю, что это то, что я делаю, или не делаю в моем методе __exit__
. Я попытался добавить код для повторного рейза GeneratorExit
, но это не помогло.
Ответы
Ответ 1
из Запись модели данных для object.__exit__
Если предоставляется исключение, и метод хочет подавить исключение (т.е. предотвратить его распространение), он должен вернуть истинное значение. В противном случае исключение будет обработано обычно после выхода из этого метода.
В вашей функции __exit__
вы возвращаете True
, который будет подавлять все исключения. Если вы измените его, чтобы вернуть 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
. Теперь вы видите только ошибку атрибута. Возможно, эмпирическое правило должно быть таким же, как с любой обработкой Exception - только перехватывать исключения, если вы знаете, как их обрабатывать. Наличие __exit__
return True
находится на одном уровне (возможно, немного хуже!), Чем на голом, кроме:
try:
something()
except: #Uh-Oh
pass
Обратите внимание, что когда AttributeError
поднят (и не пойман), я считаю, что вызывает подсчет ссылок на ваш объект-генератор до 0, который затем вызывает исключение GeneratorExit
в генераторе, чтобы он мог очистить себя вверх. Используя мой __exit__
, поиграйте со следующими двумя случаями, и, надеюсь, вы увидите, что я имею в виду:
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"
Ответ 2
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 *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