gpt4 book ai didi

Python 3.7 : How to avoid stackoverflow for this recursive approach?

转载 作者:行者123 更新时间:2023-12-01 08:08:05 25 4
gpt4 key购买 nike

1。情况

我正在用 Python 开发一个项目,并且我得到了很多以下风格的函数:

from PyQt5.QtCore import *
import functools

...

def myfunc(self, callback, callbackArg):
'''
This function hasn't finished its job when it hits the
return statement. Provide a callback function and a
callback argument, such that this function will call:
callback(callbackArg)
when it has finally finished its job.
'''
def start():
myIterator = iter(self.myList)
QTimer.singleShot(10, functools.partial(process_next, myIterator))
return

def process_next(itemIterator):
try:
item = next(itemIterator)
except StopIteration:
finish()

# Do something

QTimer.singleShot(10, functools.partial(process_next, myIterator))
return

def finish():
callback(callbackArg)
return

start()
return

该函数运行时间不会很长,因此不会卡住 GUI 和其他进程。相反,该函数几乎立即退出,并在稍后的许多短突发中完成其工作。最后 - 当工作完成时 - 它调用提供的回调。

 

2。问题

但是有一个缺点。这种方法给堆栈带来了很大的压力(我认为),因为你得到了以下链:

start() -> process_next() -> process_next() -> process_next() -> ... -> finish()

尽管我对此并不完全确定。函数process_next()调用QTimer.singleShot(...)然后退出。那么也许堆栈上的这条长链根本就没有发生?

你知道这种方法是否会带来堆栈溢出的风险吗?还有其他我没有发现的潜在风险吗?

 
编辑
谢谢@ygramoel 的澄清。所以事实上,下面这一行:

QTimer.singleShot(10, functools.partial(process_next, myIterator))

调用函数process_next(myIterator)而不推送另一个堆栈帧。因此,我不会因为长列表而冒堆栈溢出的风险。太棒了!

我只是想知道:有时我不想要 QTimer.singleShot() 函数提供的几毫秒延迟。要立即调用下一个函数(无需推送另一个堆栈帧),我可以这样做:

QTimer.singleShot(0, functools.partial(process_next, myIterator))

但是,每个 QTimer.singleShot() 调用都会触发 pyqtSignal()。在短时间内触发太多的线程会将主线程拉伸(stretch)到极限(记住:主 python 线程监听传入的 pyqt 信号)。主线程一一处理事件队列条目,调用相应的槽。因此,如果软件向该队列触发太多事件,GUI 可能会变得无响应。

是否有另一种优雅的方式来调用 process_next(myIterator) 而不会出现以下任何问题:

  • 堵塞事件队列,导致 GUI 无响应。
  • 递归函数帧导致堆栈溢出。

最佳答案

您没有包含 item.foobarself.foo 的代码。假设这些调用不会引起深度递归,则执行此代码期间的最大堆栈深度不会随着列表长度的增加而增加。

functools.partial 不会立即调用 process_next 函数。它仅创建一个可以稍后调用的类似函数的对象。请参阅https://docs.python.org/3/library/functools.html

QTimer.singleShot 也不会立即调用 process_next 函数。它安排从 functools.partial 返回的类函数对象在当前对 process_next 的调用返回后执行。

您可以通过在 process_next 开头放置 print("enter") 语句和 print("leave") 来轻松验证这一点 返回之前的语句。

在递归的情况下,您将看到:

enter
enter
enter
...
leave
leave
leave

对于很长的列表,堆栈将会溢出。

如果没有递归,您将看到:

enter
leave
enter
leave
enter
leave
...

最大堆栈深度与列表的长度无关。

关于Python 3.7 : How to avoid stackoverflow for this recursive approach?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55433605/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com