Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/python-3.x/18.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 基于生成器的协程的看似无限递归_Python_Python 3.x_Recursion_Generator_Coroutine - Fatal编程技术网

Python 基于生成器的协程的看似无限递归

Python 基于生成器的协程的看似无限递归,python,python-3.x,recursion,generator,coroutine,Python,Python 3.x,Recursion,Generator,Coroutine,以下是大卫·比兹利(David Beazley)关于发电机的幻灯片(供感兴趣的人观看) 定义了一个任务类,该类将生成未来的生成器完整地封装在任务类中(不包含错误处理),如下所示: class Task: def __init__(self, gen): self._gen = gen def step(self, value=None): try: fut = self._gen.send(value)

以下是大卫·比兹利(David Beazley)关于发电机的幻灯片(供感兴趣的人观看)

定义了一个
任务
类,该类将生成未来的生成器完整地封装在
任务
类中(不包含错误处理),如下所示:

class Task:
    def __init__(self, gen):
        self._gen = gen

    def step(self, value=None):
        try:
            fut = self._gen.send(value)
            fut.add_done_callback(self._wakeup)
        except StopIteration as exc:
            pass

    def _wakeup(self, fut):
        result = fut.result()
        self.step(result)
在示例中,还定义了以下递归函数:

from concurrent.futures import ThreadPoolExecutor
import time

pool = ThreadPoolExecutor(max_workers=8)

def recursive(n):
   yield pool.submit(time.sleep, 0.001)
   print("Tick :", n)
   Task(recursive(n+1)).step()
以下是两种情况:

  • 从Python REPL中,如果我们定义这些(或者如果将它们放在文件中,则导入它们),然后使用以下命令跳转开始递归:

    Task(recursive(0)).step()
    
    它开始打印,似乎已经超过了递归限制。但它显然没有超过它,打印堆栈级别表明它在整个执行过程中保持不变。还有别的事情我不太明白

    注意:如果这样执行python进程,则需要终止它

  • 如果我们将所有内容(
    任务
    递归
    )与以下内容一起放入文件:

    if __name__ == "__main__":
        Task(recursive(0)).step()
    
    然后用
    python myfile.py
    运行它,它在
    7
    处停止滴答(似乎是
    max_workers
    的数量)


  • 我的问题是,它似乎是如何超越递归限制的,以及为什么它的行为会根据您执行它的方式而有所不同


    该行为同时出现在Python 3.6.2和Python 3.5.4上(我猜
    3.6
    3.5
    家族中的其他成员也会出现)。

    让我们从第7个开始。这是您已经提到的工人数量,标记为[0..7]任务类需要以函数标识符的形式传递
    递归

    Task(recursive).step(n) 
    
    而不是

    Task(recursive(n)).step()
    
    这是因为,recursive函数需要在
    pool
    环境中调用,而在当前情况下,
    recursive
    是在主线程本身中计算的<代码>时间。睡眠是当前代码中唯一在任务池中计算的函数


    代码的一个主要问题是递归。池中的每个线程都依赖于内部函数,该函数对可用工作线程数的执行设置上限。该函数无法完成,因此无法执行新函数。因此,它在达到递归限制之前就终止了。

    您显示的
    递归
    生成器实际上不是递归的,不会导致系统递归限制出现问题

    了解为什么需要注意
    递归
    生成器的代码何时运行。与普通函数不同,仅调用
    recursive(0)
    不会导致它立即运行代码并进行其他递归调用。相反,调用
    recursive(0)
    会立即返回生成器对象。只有当您
    send()
    发送到生成器时,代码才会运行,并且只有在您
    send()
    第二次发送到生成器后,它才会启动另一个调用

    让我们在代码运行时检查调用堆栈。在顶层,我们运行
    任务(递归(0)).step()
    。按顺序做三件事:

  • recursive(0)
    此调用立即返回生成器对象
  • Task()
    将创建
    Task
    对象,其
    \uuuu init\uuuu
    方法存储对在第一步中创建的生成器对象的引用
  • \uuu.step()
    调用任务上的方法。这就是行动真正开始的地方!让我们看看通话中发生了什么:

    • fut=self.\u gen.send(value)
      在这里,我们通过发送一个值来启动发电机运行。让我们深入了解生成器代码的运行情况:
      • yield pool.submit(time.sleep,0.001)
        此命令计划在另一个线程中执行某些操作。但我们不会等它发生。相反,我们会得到一个
        未来的
        ,当它完成时,我们可以使用它来获得通知。我们让未来立即回到以前的代码级别
    • fut.add\u done\u callback(self.\u wakeup)
      这里我们要求在将来准备好时调用我们的
      \u wakeup()
      方法。这总是立即返回
    • 步骤
      方法现在结束。没错,我们(暂时)完成了!这对于问题的第二部分很重要,我将在后面进一步讨论
  • 我们所做的调用已结束,因此如果我们以交互方式运行,控制流将返回到REPL。如果我们作为脚本运行,解释器将到达脚本的末尾并开始关闭(我将在下面详细讨论)。然而,由线程池控制的其他线程仍在运行,并且在某个时刻,其中一个线程将执行我们关心的一些事情!让我们看看那是什么

  • 当调度函数(
    time.sleep
    )完成运行后,它所在的线程将调用我们在
    Future
    对象上设置的回调。也就是说,它将调用前面创建的
    Task
    对象上的
    Task.\u wakup()
    (我们在顶层不再对其进行引用,但
    Future
    保留了一个引用,因此它仍然处于活动状态)。让我们看看这个方法:

    • result=fut.result()
      存储延迟调用的结果。在这种情况下,这是不相关的,因为我们从不查看结果(反正是
      None
    • self.step(结果)
      再次执行步骤!现在我们回到我们关心的代码。让我们看看这次它做了什么:
      • fut=self.\u gen.send(value)
        send再次发送到生成器,以便它接管。它已经产生了一次,所以这次我们在
        产生之后开始:
        
        • 打印(“勾选:”,n)
          这很简单
        • Task(递归(n+1)).step()
          这就是事情变得有趣的地方。这一行和我们开始时一样。所以,我
          # create the pool below the definition of recursive()
          with ThreadPoolExecutor(max_workers=8) as pool:
              Task(recursive(0)).step()
          
          exception calling callback for <Future at 0x22313bd2a20 state=finished returned NoneType>
          Traceback (most recent call last):
            File "S:\python36\lib\concurrent\futures\_base.py", line 324, in _invoke_callbacks
              callback(self)
            File ".\task_coroutines.py", line 21, in _wakeup
              self.step(result)
            File ".\task_coroutines.py", line 14, in step
              fut = self._gen.send(value)
            File ".\task_coroutines.py", line 30, in recursive
              Task(recursive(n+1)).step()
            File ".\task_coroutines.py", line 14, in step
              fut = self._gen.send(value)
            File ".\task_coroutines.py", line 28, in recursive
              yield pool.submit(time.sleep, 1)
            File "S:\python36\lib\concurrent\futures\thread.py", line 117, in submit
              raise RuntimeError('cannot schedule new futures after shutdown')
          RuntimeError: cannot schedule new futures after shutdown