在上下文管理器中处理异常
Handling exceptions inside context managers
我有一些代码尝试访问资源,但有时它不可用,并导致异常。我尝试使用 上下文管理器 实现重试引擎,但我无法处理调用者在 __enter__
上下文中为我的上下文管理器引发的异常。
class retry(object):
def __init__(self, retries=0):
self.retries = retries
self.attempts = 0
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
这些只是引发异常(我希望处理)的一些示例
>>> with retry(retries=3):
... print ok
...
Attempts 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'ok' is not defined
>>>
>>> with retry(retries=3):
... open('/file')
...
Attempts 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IOError: [Errno 2] No such file or directory: '/file'
有没有办法拦截这些异常并在上下文管理器中处理它们?
要处理 __enter__
方法中的异常,最直接(也不太令人惊讶)的做法是将 with
语句本身包装在 try-except 子句中,并简单地引发异常 -
但是,with
块显然不是设计成这样工作的 - 本身就是 "retriable" - 这里存在一些误解:
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
一旦你 return self
在那里,__enter__
运行的上下文不再存在 - 如果 with
块内发生错误,它将自然流动__exit__
方法。不,无论如何,__exit__
方法不能使执行流程回到 with
块的开头。
您可能想要更像这样的东西:
class Retrier(object):
max_retries = 3
def __init__(self, ...):
self.retries = 0
self.acomplished = False
def __enter__(self):
return self
def __exit__(self, exc, value, traceback):
if not exc:
self.acomplished = True
return True
self.retries += 1
if self.retries >= self.max_retries:
return False
return True
....
x = Retrier()
while not x.acomplished:
with x:
...
引用 __exit__
,
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
默认情况下,如果您不 return 一个函数的明确值,Python 将 return None
,这是一个虚假值。在您的情况下,__exit__
returns None
这就是允许异常流过 __exit__
.
的原因
所以,return 一个真值,像这样
class retry(object):
def __init__(self, retries=0):
...
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
print exc_type, exc_val
return True # or any truthy value
with retry(retries=3):
print ok
输出将是
Attempts 1
<type 'exceptions.NameError'> name 'ok' is not defined
如果你想有重试功能,你可以用装饰器来实现,像这样
def retry(retries=3):
left = {'retries': retries}
def decorator(f):
def inner(*args, **kwargs):
while left['retries']:
try:
return f(*args, **kwargs)
except NameError as e:
print e
left['retries'] -= 1
print "Retries Left", left['retries']
raise Exception("Retried {} times".format(retries))
return inner
return decorator
@retry(retries=3)
def func():
print ok
func()
我觉得这个很简单,其他人似乎想多了。把资源获取代码放在__enter__
里,尝试return,而不是self
,而是获取资源。在代码中:
def __init__(self, retries):
...
# for demo, let's add a list to store the exceptions caught as well
self.errors = []
def __enter__(self):
for _ in range(self.retries):
try:
return resource # replace this with real code
except Exception as e:
self.attempts += 1
self.errors.append(e)
# this needs to return True to suppress propagation, as others have said
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
for e in self.errors:
print e # as demo, print them out for good measure!
return True
现在试试看:
>>> with retry(retries=3) as resource:
... # if resource is successfully fetched, you can access it as `resource`;
... # if fetching failed, `resource` will be None
... print 'I get', resource
I get None
Attempts 3
name 'resource' is not defined
name 'resource' is not defined
name 'resource' is not defined
我有一些代码尝试访问资源,但有时它不可用,并导致异常。我尝试使用 上下文管理器 实现重试引擎,但我无法处理调用者在 __enter__
上下文中为我的上下文管理器引发的异常。
class retry(object):
def __init__(self, retries=0):
self.retries = retries
self.attempts = 0
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
这些只是引发异常(我希望处理)的一些示例
>>> with retry(retries=3):
... print ok
...
Attempts 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: name 'ok' is not defined
>>>
>>> with retry(retries=3):
... open('/file')
...
Attempts 1
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
IOError: [Errno 2] No such file or directory: '/file'
有没有办法拦截这些异常并在上下文管理器中处理它们?
要处理 __enter__
方法中的异常,最直接(也不太令人惊讶)的做法是将 with
语句本身包装在 try-except 子句中,并简单地引发异常 -
但是,with
块显然不是设计成这样工作的 - 本身就是 "retriable" - 这里存在一些误解:
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
一旦你 return self
在那里,__enter__
运行的上下文不再存在 - 如果 with
块内发生错误,它将自然流动__exit__
方法。不,无论如何,__exit__
方法不能使执行流程回到 with
块的开头。
您可能想要更像这样的东西:
class Retrier(object):
max_retries = 3
def __init__(self, ...):
self.retries = 0
self.acomplished = False
def __enter__(self):
return self
def __exit__(self, exc, value, traceback):
if not exc:
self.acomplished = True
return True
self.retries += 1
if self.retries >= self.max_retries:
return False
return True
....
x = Retrier()
while not x.acomplished:
with x:
...
引用 __exit__
,
If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
默认情况下,如果您不 return 一个函数的明确值,Python 将 return None
,这是一个虚假值。在您的情况下,__exit__
returns None
这就是允许异常流过 __exit__
.
所以,return 一个真值,像这样
class retry(object):
def __init__(self, retries=0):
...
def __enter__(self):
...
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
print exc_type, exc_val
return True # or any truthy value
with retry(retries=3):
print ok
输出将是
Attempts 1
<type 'exceptions.NameError'> name 'ok' is not defined
如果你想有重试功能,你可以用装饰器来实现,像这样
def retry(retries=3):
left = {'retries': retries}
def decorator(f):
def inner(*args, **kwargs):
while left['retries']:
try:
return f(*args, **kwargs)
except NameError as e:
print e
left['retries'] -= 1
print "Retries Left", left['retries']
raise Exception("Retried {} times".format(retries))
return inner
return decorator
@retry(retries=3)
def func():
print ok
func()
我觉得这个很简单,其他人似乎想多了。把资源获取代码放在__enter__
里,尝试return,而不是self
,而是获取资源。在代码中:
def __init__(self, retries):
...
# for demo, let's add a list to store the exceptions caught as well
self.errors = []
def __enter__(self):
for _ in range(self.retries):
try:
return resource # replace this with real code
except Exception as e:
self.attempts += 1
self.errors.append(e)
# this needs to return True to suppress propagation, as others have said
def __exit__(self, exc_type, exc_val, traceback):
print 'Attempts', self.attempts
for e in self.errors:
print e # as demo, print them out for good measure!
return True
现在试试看:
>>> with retry(retries=3) as resource:
... # if resource is successfully fetched, you can access it as `resource`;
... # if fetching failed, `resource` will be None
... print 'I get', resource
I get None
Attempts 3
name 'resource' is not defined
name 'resource' is not defined
name 'resource' is not defined