我已经编写了以下函数,但是正在考虑是否有更好的方法?
我想重试的示例函数
def is_user_exist(username):
try:
pwd.getpwnam(username)
log.info("User %s exist" % username)
return True
except KeyError:
log.exception("User %s does not exist." % username)
return False
我的重试函数
def retry(func, *func_args, **kwargs):
retry_count = kwargs.get("retry_count", 5)
delay = kwargs.get("delay", 5)
while retry_count > 1:
if func(*func_args):
return
log.debug("waiting for %s seconds before retyring again")
sleep(delay)
retry_count = retry_count - 1
return func(*func_args)
/>
#1 楼
我喜欢所有的Ev。 Kounis的回答,所以我将添加一些更高级的详细信息。说实话-y
现在,您并不严格要求
func
返回True
或False
,只不过是对或错。很好,并且是Pythonic。我认为您将受益于在文档字符串中指出这一点(您也应该编写)。另外,可能值得返回func(*func_args)
的实际值,而不是True
或False
;这样,如果您想利用值的真实性,就可以从函数中实际获取值。更好的
kwargs
支持您也不要允许将任何关键字参数传递给函数-函数可能支持它们,因此您应该这样做。我会将您从
kwargs
中拉出的两个关键字参数提升为具有适当默认值(在您的情况下为5)的显式关键字参数。例外情况函数确实具有在异常之后重试的任何概念。我不希望您这样做,因为我希望这是显而易见的原因
for _ in range(retry_count):
try:
if func(*func_args):
return True
except:
pass
log.debug("wating for %s seconds before retrying again")
sleep delay)
但是,在很多情况下,我怀疑您会知道您可能会遇到哪些例外情况期望(例如,网络超时或连接性故障),并且您可能希望由重试框架处理。为此,我认为这样可能会很好:
def retry(func, *args, retry_count=5, delay=5, allowed_exceptions=(), **kwargs):
for _ in range(retry_count):
try:
result = func(*args, **kwargs)
if result: return result
except allowed_exceptions as e:
pass
这显然不是完整的实现;我遗漏了您拥有的其他部分,如果它在上一次迭代中失败,它的行为会很奇怪,但是应该足以启动。认为如果它是装饰者,我们还可以从中获得更多价值。这样,函数的使用者甚至不需要知道是否要重试该函数。他们只是调用它们的函数,然后看它是否起作用,并且是否重试就变得无关紧要了。不要忘记使用
functools.wraps
来保存元数据。import functools
def retry(retry_count=5, delay=5, allowed_exceptions=()):
def decorator(f):
@functools.wraps(f)
def wrapper(*args, **kwargs):
for _ in range(retry_count):
# everything in here would be the same
return wrapper
return decorator
然后您可以对所有人启用重试,如下所示:当你在等什么?使用
asyncio
可能会做更多的事情(这是针对Python 3.5及更高版本的)。在此之前,没有内置的支持,但是我知道有些异步框架应该能够完成相同的任务。 ,您可以达到相同的目标,但是在等待时允许其他异步功能运行。请注意,根据事件循环,您可能最终会等待更多或更少的时间。我还清理了我提到的处理异常的问题;现在,它总是返回该函数的结果(如果有一个结果),并且它将重新引发该异常,而不会丢失任何回溯(如果有的话)。这还使用了仅Python 3的功能;我已经对如何在Python 2中进行操作发表了评论。
注意,我对asyncio并不熟悉,因为我从来没有在那里做过任何认真的开发工作,因此我可能没有这个一段代码完全正确;理论应该是正确的。
@retry(retry_count=5, delay=5)
def is_user_exist(username):
try:
pwd.getpwnam(username)
log.info("User %s exist" % username)
return True
except KeyError:
log.exception("User %s does not exist." % username)
return False
评论
\ $ \ begingroup \ $
很棒的帖子!如果我想通过重试获得价值,该怎么办?例如,如果我有一个last_page来检查函数内的每个迭代。当我重试时,我不想再做一遍。但是从该last_page开始。可能吗?
\ $ \ endgroup \ $
– salvob
18年9月12日在15:04
\ $ \ begingroup \ $
@salvob毫无头绪!如果您可以正常使用,请随时发布代码以供审核:)
\ $ \ endgroup \ $
–丹农
18年9月12日在18:05
#2 楼
我唯一注意到的是retry
的行为可能会不一致。让我解释一下:如果在
func
内部检查它成功,则会返回while
。另一方面,如果在None
之外成功执行,它将返回
while
返回的任何内容(在您的示例func
中)。您不想拥有它。.所以我建议稍微重新编码:
def retry(func, *func_args, **kwargs):
retry_count = kwargs.get("retry_count", 5)
delay = kwargs.get("delay", 5)
while retry_count > 1:
if func(*func_args):
return
log.debug("waiting for %s seconds before retyring again")
sleep(delay)
retry_count = retry_count - 1
return func(*func_args)
如果愿意,可以替换上面的
True
循环,以得到一些幻想:只要不返回for
就没有关系。评论
\ $ \ begingroup \ $
严格来说,它可能不会在循环外返回True。它会返回func所做的任何事情,这可能看起来像是真或假。
\ $ \ endgroup \ $
–丹农
18年2月28日在16:17
\ $ \ begingroup \ $
@Dannnno正确,我只是指OP的示例函子。
\ $ \ endgroup \ $
– Ma0
18年2月28日在16:19
\ $ \ begingroup \ $
您不会将kwargs传递给func,是吗?
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18年2月28日在21:50
#3 楼
理论您的
retry
函数与any
的结构非常相似。仅保留基本内容,您可以将
retry
编写为: br /> 代码
如果要
kwargs
,log
和sleep
,可以编写:any(func(*func_args) for _ in range(count))
注意事项
log.debug
和time.sleep
都是虚假的,所以当且仅当func or log or time
是真实的时,func
才是真实的。 。否则,它们将传递给dict.pop
。完整代码
def retry(func, *func_args, **kwargs):
count = kwargs.pop("count", 5)
delay = kwargs.pop("delay", 5)
return any(func(*func_args, **kwargs)
or log.debug("waiting for %s seconds before retyring again" % delay)
or time.sleep(delay)
for _ in range(count))
评论
\ $ \ begingroup \ $
我不是在这里使用它的忠实拥护者;如果您想要的功能集非常有限,那么它可以很好地工作并且非常简单,但是了解为什么要这样做是有点不直观的。我还认为将所有内容组合在一起很难看-应该是一个单独的功能imo。
\ $ \ endgroup \ $
–丹农
18年2月28日在22:00
\ $ \ begingroup \ $
@Dannnno:感谢您的评论。我猜对每个人来说。 OP的基本兴趣在于知道func是否会在计数时间内至少工作一次,这几乎就是目的。其余的都是可选的装饰恕我直言。
\ $ \ endgroup \ $
–埃里克·杜米尼尔(Eric Duminil)
18-2-28在22:21
#4 楼
以下是您当前设置的一些问题:被重试的函数不能使用关键字参数。在大多数情况下,可以很容易地解决此问题,但是允许函数接受类似
delay
的参数会更加复杂。这使得避免重复变得更加困难。您最终可能会在日志中连续出现5次相同的回溯。当您必须添加额外的行时,这对于某些函数将是令人讨厌的,对于返回值有效的其他函数则是可怕的。我前一阵子写了这个装饰器,并愉快地在几个地方使用了它。这个想法与Dannnno的答案类似,但是我只在出现异常后重试,而不注意返回值。 br />
def retry(num_attempts=3, exception_class=Exception, log=None, sleeptime=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(num_attempts):
try:
return func(*args, **kwargs)
except exception_class as e:
if i == num_attempts - 1:
raise
else:
if log:
log.error('Failed with error %r, trying again', e)
sleep(sleeptime)
return wrapper
return decorator
这是另一个示例,我仅在调用站点重试,而不更改函数的定义:
您的案件有点不寻常,因为其中涉及一个例外,但您最终不想提出该例外。您也许可以按照以下方式重新编写代码:
from requests.exceptions import ConnectionError
@retry(5, ConnectionError, log)
def request(self, method, url='', **kwargs):
...
成为:当条件为假但不涉及异常的情况下要重试时,可以显式引发异常:
在谈论如何编写一个非常通用且可广泛应用的功能,但是我们只有一个用例可以应用它。如果您有其他要重试的代码示例,则可以使此讨论更为有用。
#5 楼
我很惊讶没有人提到韧性库。它完全可以满足您的要求+已有一个异步内置的实现。您还可以使用相当宽容的参数(等待然后重试,等待x时间等)。
评论
\ $ \ begingroup \ $
可能是因为它不是stdlib,而仅包含一个模块用于某些您可以轻松编写代码的模块,因为这样做成本很高。
\ $ \ endgroup \ $
–
19-10-23在14:49
#6 楼
您可以通过range()通过简单的for循环替换while循环和retry_count
倒数计时def retry(func, *func_args, **kwargs):
retry_count = kwargs.get("retry_count", 5)
delay = kwargs.get("delay", 5)
for _ in range(retry_count):
if func(*func_args):
return
log.debug("waiting for %s seconds before retyring again")
sleep(delay)
return func(*func_args)
评论
\ $ \ begingroup \ $
您的观点已经在另一个较早的答案中提出。我建议编辑此答案以使其添加新值,或者删除它。
\ $ \ endgroup \ $
– janos
18年2月28日在18:57
\ $ \ begingroup \ $
@janos发布后看到了,但我不知道如何在移动应用程序中删除此发布。当我进入桌面网站时,我会
\ $ \ endgroup \ $
–user171782
18年2月28日在20:09
评论
请不要更新您问题中的代码以合并答案的反馈,因为这样做有悖于“代码审阅”的“问题+答案”风格。这不是一个论坛,您应该在其中保留问题的最新版本。收到答案后,请查看您可能会做什么和可能不会做什么。@PatrickMevzek除非我误读了该帖子,否则似乎无法使用上下文管理器
以我的经验,重试功能是一开始的代码味道。
@ jpmc26为什么?在许多情况下,自动重试是有效的(联网是最明显的例子)
@Dannnno通常,这表明您在错误地进行了请求,就像调用sleep一样。 OP的特定示例是本地的:如果用户还不在本地,为什么不久之后会出现在那里?在最好的情况下,他们有一些异步运行的进程可能会创建它。但是,更可靠的方法是在创建用户后等待队列中的条目。如果您谈论的是诸如实施TCP之类的低级内容,那您有意思。但是当今大多数代码都是在较高的级别上编写的,这些级别上的失败通常表示重试将无法进行。