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

时间:2010-06-19 07:44:56

标签: python function generator yield

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

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        f(self)
        #do some tear down

现在,我也(更准确地说,想要)一个函数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

取而代之的是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)

因此,如果我选择将f设为生成器,我还需要将g设为生成器,并h,否则此技巧将无效。

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

(我知道这个问题很长很丑 - 这是我能做的最好的。如果有什么不清楚,请告诉我,我会澄清它)

修改

也许pep 342是解决方案?

5 个答案:

答案 0 :(得分:3)

我之前的回答描述了如何在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)

结果是:

generator yields 0
generator yields 1
generator yields 2
returned 3
generator yields 4

例外情况会以您期望的方式传递。

答案 1 :(得分:2)

我相信我也应该从另一个角度添加一个答案,即不试图解释如何实现我们能够理解你想要做什么,但为什么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

然后你应该明白,控制流量并不是很大,而是将调用上下文保留在变量中。一旦理解,你必须决定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

但你不应该关心f()是否必须调用内部函数g()等。这完全无关紧要。您提供了一个lib,使用适当的iterable调用是您的用户问题。如果您认为您的lib用户将无法使用,则必须更改整体设计。

希望它有所帮助。

答案 2 :(得分:1)

我也不了解整个(main_hook调用者看起来是什么样的?),但我会说,当你应该停止时抛出一个StopNow异常,就像你应该在你的生成器完成时抛出StopIteration一样。 / p>

这是我如何理解这件事以及我会做什么。

class StopNow(Exception):
    pass

def main_hook(self,f):
    got_stop_now_exc = False
    while (!got_stop_now_exc and self.shouldContinue()):
        #do some preparations
        try:
             f(self)
        except StopNow:
             got_stop_now_exc = True

        #do some compulsary tear down, exception or not

def stop_and_do_stuff()
    raise StopNow()
def my_f():
    if needed:
        stop_and_do_stuff()

def the_main_hook_caller():
    while i_should:
        managerthingie.main_hook(my_f)
        do_stuff()

答案 3 :(得分:0)

我不太清楚你究竟想要实现什么,所以如果你可以更多地解释问题而不是提供更好的解决方案。

从我的部分理解为什么你不做这样的事情

def main_hook(self,f):
    while (self.shouldContinue()):
        #do some preparations
        stop_and_do_stuff = f(self)
        if stop_and_do_stuff :
            yield

        #do some tear down

所以基本上f会返回一个停止或不停止的标志,如果它表示停止,我们会调用main_hook函数,并且该函数可以在执行某些操作后继续执行

e.g。

class A(object):
    def main_hook(self,f):
        while (self.shouldContinue()):
            #do some preparations
            stop = f(self)
            if stop:
                yield

            #do some tear down

    def shouldContinue(self):
        return True

def f(a):
    return True

a = A()
for x in a.main_hook(f):
    print x

答案 4 :(得分:0)

您描述的行为看起来就像一个简单的函数调用。如下所示。

def f(manager):
    print("Entering f")
    manager.stop_and_do_stuff()
    print("Exiting f")

class Manager(Object):
    def shouldContinue(self):
        return True

    def stop_and_do_stuff(self):
        print("Manager stop and do stuff")

    def main_hook(self,f):
        while self.shouldContinue()
            print("Manager Setup")
            f(self)
            print("Manager Tear Down")

如果另一个用户提供了f(),如果从某个内部函数调用了stop_and_do_stuff,则没问题。如果您还希望管理器能够从stop_and_do_stuff展开堆栈并在某些情况下真正退出,则没问题。只是从中引出一些异常,你就可以从main_hook或高级代码中捕获它。

你应该可以从stop_and_and_do_stuff()内部做任何你想做的事情来自主钩的调用者。如果没有,你应该解释原因。

问题中不清楚的是main_hook()的调用方发生了什么,以及为什么你希望能够退出main_hook循环,但实际上并不是这样。 main_loop调用者要求生成器要么不要求生成器。如果你想得到一个明智的答案,你需要解释那个部分(一些上下文信息也会很好,如果你真的解释你试图做的WTF,你真正的限制 - 你说f是由其他一些用户和main_hook提供的在lib中,main_hook调用者是什么? - 可能是众所周知的常用解决方案。)