在另一个函数中调用生成器的yield

ola*_*ndo 9 python yield function generator

假设我有一些经理对象.这个对象的API有一个main_hook函数,它获取另一个函数f作为它的参数,并f在循环中运行给定的东西,在每次迭代之间做一些事情:

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        f(self)
        #do some tear down
Run Code Online (Sandbox Code Playgroud)

现在,我也有(更确切地说,想要)一个函数stop_and_do_stuff,一旦被调用,就停止main_hook在它的轨道中,将控制返回到所调用的函数main_hook,然后在该函数完成它正在做的事情之后,将控制权交还给main_hook和继续.基本上结果与做的一样

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        yield
        #do some tear down
Run Code Online (Sandbox Code Playgroud)

除此之外,yield我想打电话给f(),同时给出f打电话的选项self.stop_and_do_stuff()

不能通过使f也成为发电机来解决这个问题有两个原因:

1. f不是我的API的一部分 - 它是由使用我的lib的用户提供给我的

2.即使可以要求他使用yield,他需要调用的代码中的位置stop_and_do_stuff也不会直接位于f内,而是位于函数堆栈中的某个位置f(),而不是直接位于其中,例如

def h(manager):
    #do stuff
    if should stop:
        manager.stop_and_do_stuff()
    #do more stuff
def g(manager):
    #some stuff
    if should stop:
        manager.stop_and_do_stuff()
    #more stuff
    if should stop again:
        manager.stop_and_do_stuff()  
    if should call h:
        h()
def f(manager):
    g(manager)
Run Code Online (Sandbox Code Playgroud)

因此,如果我选择制造f发电机,我还需要制造g发电机h,否则这个技巧将不起作用.

这有什么解决方案吗?也许我试图以错误的方式解决它?

(我知道这个问题很长很难看 - 这是我能做的最好的事情.如果事情不清楚,请告诉我,我会澄清一下)

编辑

也许pep 342是解决方案?

Bas*_*nen 8

我之前的回答描述了如何在Python2中做到这一点,这是非常难看的。但现在我遇到了PEP 380:委托给子生成器的语法。这正是你所要求的。唯一的问题是它需要Python3。但这应该不是什么问题。

它的工作原理如下:

def worker():
    yield 1
    yield 2
    return 3

def main():
    yield 0
    value = yield from worker()
    print('returned %d' % value)
    yield 4

for m in main():
    print('generator yields %d' % m)
Run Code Online (Sandbox Code Playgroud)

结果是:

generator yields 0
generator yields 1
generator yields 2
returned 3
generator yields 4
Run Code Online (Sandbox Code Playgroud)

异常会按照您期望的方式传递。


kri*_*iss 4

我相信我还应该从另一个角度添加一个答案,即不要试图解释你如何实现我们可以理解你想要做的事情,但为什么肯定yield不可能工作。

当函数包含yield关键字时,它会被深度修改。它仍然是一个可调用的函数,但不再是一个普通的函数:它变成了一个返回迭代器的工厂。

从调用者的角度来看,下面的三种实现没有任何区别(除了一个yield更简单)。

##########################################
print "Function iterator using yield",

def gen():
    for x in range(0, 10):
        yield x

f = gen()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen():
    print x,

print

#########################################
print "Class iterator defining iter and next",

class gen2(object):

    def __init__(self):
        self.index = 0;
        self.limit = 10;

    def __iter__(self):
        return self

    def next(self):
        if self.index >= self.limit:
            raise StopIteration
        self.index += 1;
        return self.index - 1;


f = gen2()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen2():
    print x,
print

#########################################
print "Function iterator using iter() and sentinel",
def gen3():
    def g3():
        if g3.index is None:
            g3.index = 0
        g3.index += 1;
        return g3.index - 1

    g3.index = None
    return iter(g3, 10)

f = gen3()
try:
    while True:
        print f.next(),
except StopIteration:
    pass

for x in gen3():
    print x,
print
Run Code Online (Sandbox Code Playgroud)

那么你应该明白,yield 与控制流无关,而是将调用上下文保留在变量中。一旦理解了这一点,您就必须决定 main_loop 的 API 是否真的想为其调用者提供迭代器。那么如果是这样,如果 f 可以循环,它也必须是一个迭代器(并且应该有一个围绕 f() 调用的循环,如下所示)。

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        for v in f(self):
            yield v
        #do some tear down
Run Code Online (Sandbox Code Playgroud)

但是你不应该关心 f() 是否必须调用内部函数 g() 等。这是完全不相关的。您提供了一个库,并且使用适当的可迭代对象进行调用是您的用户问题。如果您认为您的库用户无法做到这一点,您将不得不更改整体设计。

希望能帮助到你。