Python 使用decorator恢复发电机
让我们来看一个类,它的函数有时会失败,但经过一些操作之后,它就可以完美地工作了 现实生活中的例子是Mysql查询,它引发了Python 使用decorator恢复发电机,python,python-3.x,decorator,yield,Python,Python 3.x,Decorator,Yield,让我们来看一个类,它的函数有时会失败,但经过一些操作之后,它就可以完美地工作了 现实生活中的例子是Mysql查询,它引发了\u Mysql\u异常。OperationalError:(2006,“Mysql服务器消失了”)但在客户端重新连接后,它工作正常 我试着为此编写decorator: def _auto_reconnect_wrapper(func): ''' Tries to reconnects dead connection ''' def inner(se
\u Mysql\u异常。OperationalError:(2006,“Mysql服务器消失了”)
但在客户端重新连接后,它工作正常
我试着为此编写decorator:
def _auto_reconnect_wrapper(func):
''' Tries to reconnects dead connection
'''
def inner(self, *args, _retry=True, **kwargs):
try:
return func(self, *args, **kwargs)
except Mysql.My.OperationalError as e:
# No retry? Rethrow
if not _retry:
raise
# Handle server connection errors only
# http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html
if (e.code < 2000) or (e.code > 2055):
raise
# Reconnect
self.connection.reconnect()
# Retry
return inner(self, *args, _retry=False, **kwargs)
return inner
class A(object):
...
@_auto_reconnect_wrapper
def get_data(self):
sql = '...'
return self.connection.fetch_rows(sql)
好的,前面的示例不起作用,因为内部函数已经返回了生成器,在调用第一个next()
后它将中断
据我所知,如果python在方法内部看到yield
,它只会立即产生控制(不执行一条语句),并等待第一个next()
我已通过替换以下内容使其正常工作:
return func(self, *args, **kwargs)
与:
但我很好奇是否有更优雅(更像蟒蛇)的方法来做到这一点有没有办法让python先运行所有代码,然后再等待?
我知道可以只调用返回元组(func(self,*args,**kwargs))
,但我希望避免一次加载所有记录
有没有办法让python先运行所有代码,然后再等待
是的,它被称为next(您的\u生成器)
。调用next()
一次,代码将在第一次yield
之后完全等待。如果不想丢失第一个值,可以将另一个yield
放在循环的正前方
如果您使用的是python 3.3+,还可以替换
for row in func(self, *args, **kwargs):
yield row
首先,使用func(self,*args,**kwargs)的收益率,我认为您当前使用的解决方案很好。当您装饰一个生成器时,装饰器至少需要像该生成器上的迭代器一样工作。这样做,使装饰发电机,也完全可以。正如x3al所指出的,对func(…)中的行使用
yield from func(…)
而不是:yield row
是一种可能的优化
如果您想避免实际将装饰器变成生成器,可以使用next
,它将一直运行到第一个yield
,并返回第一个yield值。除了生成器生成的其余值之外,您还需要让装饰器以某种方式捕获并返回第一个值。您可以通过以下方式实现:
您还可以使装饰器同时使用生成器和非生成器函数,使用来确定您是否正在装饰生成器:
def _auto_reconnect_wrapper(func):
''' Tries to reconnects dead connection
'''
def inner(self, *args, _retry=True, **kwargs):
try:
gen = func(self, *args, **kwargs)
if inspect.isgenerator(gen):
value = next(gen)
return itertools.chain([value], gen)
else: # Normal function
return gen
except StopIteration:
return gen
except Mysql.My.OperationalError as e:
...
# Retry
return inner(self, *args, _retry=False, **kwargs)
return inner
我赞成 >/Cult>基于的解决方案,除非你有必要除了生成生成器来修饰正则函数。
你应该使用<代码>下一步(你的生成器),<代码> Obj.NEXT()>代码>已经在Python 3.XI中像这个答案(+1-ED)被删除了,但是当<代码>下(Gen)时情况如何?risesStopIteration
?@Vyktor我已经编辑了我的答案来处理这个问题。您可以捕获该异常并返回generator对象,如果尝试对其进行迭代,该对象将不起作用(或者如果对其调用next
,则再次引发StopIteration
)。老实说,这就是我建议在get\u data
中的循环之前添加另一个yield
的原因。不过,dano的解决方案要清楚得多。@x3al我真的不喜欢编辑修饰函数以使其与修饰器正常工作的想法。因为如果出于某种原因删除装饰器,get\u data
现在被破坏。对于阅读get_data
而不阅读decorator的人来说,这也很奇怪。
for row in func(self, *args, **kwargs):
yield row
def _auto_reconnect_wrapper(func):
''' Tries to reconnects dead connection
'''
def inner(self, *args, _retry=True, **kwargs):
gen = func(self, *args, **kwargs)
try:
value = next(gen)
return itertools.chain([value], gen)
except StopIteration:
return gen
except Mysql.My.OperationalError as e:
...
# Retry
return inner(self, *args, _retry=False, **kwargs)
return inner
def _auto_reconnect_wrapper(func):
''' Tries to reconnects dead connection
'''
def inner(self, *args, _retry=True, **kwargs):
try:
gen = func(self, *args, **kwargs)
if inspect.isgenerator(gen):
value = next(gen)
return itertools.chain([value], gen)
else: # Normal function
return gen
except StopIteration:
return gen
except Mysql.My.OperationalError as e:
...
# Retry
return inner(self, *args, _retry=False, **kwargs)
return inner