リソースに到達しようとするコードがありますが、使用できない場合があり、例外が発生します。 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'
これらの例外をインターセプトして、コンテキストマネージャ内で処理する方法はありますか?
引用 __exit__
、
例外が提供され、メソッドが例外を抑制したい場合(つまり、伝播されないようにする場合)、true値を返します。それ以外の場合、このメソッドの終了時に例外は通常どおり処理されます。
デフォルトでは、関数から明示的に値を返さない場合、Pythonは偽の値であるNone
を返します。あなたの場合、__exit__
はNone
を返します。そのため、例外は__exit__
を通過できます。
だから、このような真実の値を返す
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__
メソッドの例外を処理するには、with
ステートメント自体をtry-except句でラップし、単に例外-
しかし、with
ブロックは、このように機能するように設計されていません。つまり、それ自体が「再帰可能」であるため、ここでは誤解があります。
def __enter__(self):
for _ in range(self.retries):
try:
self.attempts += 1
return self
except Exception as e:
err = e
そこに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:
...
これは簡単だと思いますが、他の人々はそれを考えすぎているようです。リソース取得コードを__enter__
を返してみてください。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