Python 使用decorator恢复发电机

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

让我们来看一个类,它的函数有时会失败,但经过一些操作之后,它就可以完美地工作了

现实生活中的例子是Mysql查询,它引发了
\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)时情况如何?rises
StopIteration
?@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