【问题标题】:invoking yield for a generator in another function在另一个函数中为生成器调用 yield
【发布时间】:2023-03-03 00:52:01
【问题描述】:

假设我有一些经理对象。这个对象的 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,在 func 完成它的工作后,将控制权返回给 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 的一部分 - 它是由使用我的库的用户提供给我的

2.即使可以要求他使用yield,他需要调用stop_and_do_stuff的代码中的位置也不会直接在f内部,而是在函数堆栈中的某个位置,该位置将在@内部987654336@,但不是直接在里面,例如

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 是解决方案?

【问题讨论】:

  • 我和阿努拉格有同样的理解,也认为(像他一样)你真的没有问问题,而是提供了你自己的解决方案的元素(这还行不通)。所以你能期望的最好的就是让你的解决方案起作用,而不是得到一个真正的 Pythonic 解决方案。另外,从我在问题中看到的情况来看,我有一种奇怪的感觉。将函数称为“做某事”而不是“返回结果”对我来说似乎很奇怪,听起来你正在做的主要是一些交互式副作用。是吗?
  • 我不清楚如果 f 是一个外部库函数,它怎么能在中间调用 stop_and_do_stuff ,如果它可以做到,为什么它不能屈服?
  • @Anurag-f 得到一个manager 对象作为它的参数,它有函数 stop_and_do_stuff

标签: python function generator yield


【解决方案1】:

我不太确定你到底想达到什么目标,所以也许你能更多地解释问题而不是给出更好的解决方案。

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

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 的函数,并且该函数可以在做一些事情后继续

例如

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

【讨论】:

  • 这个解决方案远非预期的结果。重点是f 可以在中间调用stop_and_do_stuff,而无需完成(即好像f 在其中有一个yield)。在您的解决方案中,f 必须完成并返回一个标志,这会强制 f 在调用 stop_and_do_stuff 之前完成。关于我的问题,如果您能详细说明我的解释中不清楚的地方,我很乐意澄清。
  • @noam:部分不清楚的是“stop_and_do_stuff”中的“stop_and_”。要么你想像返回一样停止 f(),这就是 Anurag 所理解的,要么它只是 f() 内部的一个常用函数调用(在这种情况下也很容易,你只需要提供一个函数来调用)。看起来你想调用一个函数,但没有明确地提供它。在 python 中通常使用 yield 是另一种方式(交还控制权),你的使用看起来更像是 ruby​​ 的 yield (调用匿名函数)。更大的问题是你没有解释你想要达到的目标,而只是解释你是如何做到的。
  • 这是迄今为止最好的解决方案,但它改变了main_hook,这不是所要求的
【解决方案2】:

我也不明白整体(main_hook 调用者是什么样的?),但我想说,当你应该停止时抛出一个 StopNow 异常,就像你应该在生成器完成时抛出 StopIteration 一样。

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

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()

【讨论】:

  • 问题不是“我如何中止该功能?”,而是“我如何以一种以后可以恢复的方式暂停它?”生成器正是这样做的,使用yield。唯一的问题是,如果生成器想要调用一个应该产生的函数,那么该函数本身就会变成一个生成器,这会破坏一切。问题是如何绕过它。我已经发布了我目前所做的作为答案,但我当然对更优雅的解决方案感兴趣。
【解决方案3】:

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

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")

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

你应该可以在stop_and_and_do_stuff() 内部做任何你想从主钩子的调用者那里做的事情。如果不是,你应该解释原因。

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

【讨论】:

  • 据我了解(至少,我自己有什么问题),关键是他想编写一个复杂的生成器,它不会(仅)“直接”生成,而是还调用将产生它的函数。但是,当将 yield 放入函数时,该函数本身就变成了一个生成器,所以这不起作用。我自己的答案在这里显示了我丑陋的解决方法。如果您知道更优雅的方法,请告诉我们。 PEP 342 谈到了协程,这正是我们想要的。我们想从协程中调用函数。这似乎是不可能的。
  • main_hook 的流程应该像发电机一样由外部用户随意启动和停止。
【解决方案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

那么你应该明白,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

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

希望对你有帮助。

【讨论】:

    【解决方案5】:

    我之前的回答描述了如何在 Python2 中做到这一点,非常难看。但是现在我遇到了PEP 380: Syntax for Delegating to a Subgenerator。这正是你所要求的。唯一的问题是它需要 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
    

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

    【讨论】:

    • 这并没有解决它......你让main 现在直接使用yield,不像问题状态。
    • 这些收益只是为了显示执行的顺序。 “yield 0”和“yield 4”可以去掉。 “yield from”命令仍然有效(也意味着 main 本身就是一个生成器函数)。
    • 没错。 Main 不能成为生成器。
    • 可以,而且必须。没有其他方法可以做到这一点。正如 OP 在最后所写的那样, f 和 g 和 h 都必须是生成器。这在 Python2 中非常麻烦,但使用“yield from”它非常优雅。当然,有一个选项可以不让 f 成为生成器,但在这种情况下,它也不能等待另一个生成器(既不能等待它本身,也不能等待它调用的任何函数)。如果这是用户想要的,库可以调用 f,查看结果是否是生成器,如果是,则对其进行迭代。否则,使用(或丢弃)返回值。
    • 问题说明不能,所以如果必须回答无效。
    猜你喜欢
    • 2021-12-28
    • 2021-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-03
    • 2017-08-29
    • 2016-03-19
    • 1970-01-01
    相关资源
    最近更新 更多