【问题标题】:How can I yield notifications and return a result from a function? (Python)如何产生通知并从函数返回结果? (Python)
【发布时间】:2018-09-06 13:18:27
【问题描述】:

我必须创建一个在内部调用中做一些艰苦工作的函数。这个函数需要是一个生成器,因为我使用的是服务器发送事件。所以,我希望这个函数通过使用“产量”来通知计算的进度。之后,该函数必须将结果传递给父函数才能继续进行其他计算。

我想要这样的东西:

def hardWork():
    for i in range(N):
        # hard work
        yield 'Work done: ' + str(i)

    # Here is the problem: I can't return a result if I use a yield
    return result             

def generator():
    # do some calculations
    result = hardWork()
    # do other calculations with this result
    yield finalResult

我找到了一个解决方案,该解决方案包括产生一个字典,告诉函数是否已完成,但执行此操作的代码非常脏。

还有其他解决方案吗?

谢谢!

编辑

我的想法是这样的:

def innerFunction(gen):
    calc = 1

    for iteration in range(10):
        for i in range(50000):
            calc *= random.randint(0, 10)
        gen.send(iteration)

    yield calc


def calcFunction(gen):
    gen2 = innerFunction(gen)
    r = next(gen2)

    gen.send("END: " + str(r + 1))
    gen.send(None)


def notifier():
    while True:
        x = yield
        if x is None:
            return
        yield "Iteration " + x


def generator():
    noti = notifier()
    calcFunction(noti)
    yield from noti


for g in generator():
    print(g)

但我收到此错误:

TypeError: can't send non-None value to a just-started generator

【问题讨论】:

标签: python generator yield server-sent-events


【解决方案1】:

Python3.5 之前:生成器

此解决方案也适用于较新的 Python 版本,尽管 Python3.5 中的新版本 async def 似乎更适合您的用例。 请参阅下一节

生成器产生的值是通过迭代或使用next 获得的。最后返回的值存储在StopIteration 异常的value 属性中,指示生成器的结束。幸运的是,恢复起来并不难。

def hardWork():
    output = []

    for i in range(10):
        # hard work
        yield 'Doing ' + str(i)
        output.append(i ** 2)

    return output

def generator():
    # do some calculations
    work = hardWork()

    while True:
        try:
            print(next(work))
        except StopIteration as e:
            result = e.value
            break

    yield result

示例

foo = generator()
print(next(foo))

输出

Doing 0
Doing 1
Doing 2
Doing 3
Doing 4
Doing 5
Doing 6
Doing 7
Doing 8
Doing 9
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Python3.5+:async def

如果您运行的是 Python3.5+,那么您正在尝试的内容似乎非常适合使用可等待函数的事件循环。

import asyncio

async def hardWork():
    output = []

    for i in range(10):
        # do hard work
        print('Doing ', i)
        output.append(i**2)

        # Break point to allow the event loop to do other stuff on the side
        await asyncio.sleep(0)

    return output

async def main():
    result = await asyncio.wait_for(hardWork(), timeout=None)
    print(result)

loop = asyncio.get_event_loop()

loop.run_until_complete(main())

输出

Doing  0
Doing  1
Doing  2
Doing  3
Doing  4
Doing  5
Doing  6
Doing  7
Doing  8
Doing  9
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

【讨论】:

    【解决方案2】:

    我的建议是将你的功能嵌入到一个类中。

    def Worker:
    
        def hardWork(self):
            self.Finished = False
            for i in range(10):
                yield 'Work done: ' + str(i)
            self.Finished = True
            self.Result = 'result'
    
        def generator(self):
            while (not self.Finished):
                print(next(self.hardWork()))
            return self.Result
    

    这将具有您想要的功能,而不必担心围绕异常抛出-捕获逻辑编写逻辑。

    【讨论】:

      【解决方案3】:

      Python 3.3 引入了yield from for generator delegation,这几乎正是您所要求的。 yield from 允许主生成器将功能委托给另一个也包含 yield 语句的函数,如下所示:

      def hardWork():
          for i in range(N):
              # hard work
              yield 'Work done so far: ' + str(i)
      
          # With this construction, you can still return a result after the `yield`s
          return result
      
      def generator():
          # here's the new construction that makes it all work:
          result = yield from hardWork()
      
          # do other calculations with this result
          yield finalResult
      

      【讨论】:

        猜你喜欢
        • 2020-09-25
        • 2021-01-22
        • 2018-02-21
        • 2012-12-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-27
        相关资源
        最近更新 更多