我有一个 GUI 项目,它使用 PySide2 和 Python 3.8,它在
QThread
中执行一些后台任务。在该
QThread
中,我有
QTimer
成员对象,该对象必须定期运行一个函数,每次向其传递不同的数据。我没有使用
QTimer.singleShot
静态函数,因为如果需要某些特定场景,我需要停止/重置计时器。
目标函数成功运行后,它会调用另一个函数,该函数负责将新数据传递给计时器并再次运行它。该函数与
timer.timeout.connect(lambda: function_name(data))
连接,在向其传递新数据之前,我会停止并断开前一个插槽的连接,如果计时器处于活动状态,则停止计时器。在计时器第二次运行时,当我向其传递新数据时,目标函数仍然以某种方式使用旧数据运行。
以下代码是以隔离方式重现的问题:
from PySide2.QtCore import QTimer
from PySide2 import QtWidgets
import time
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
FORMAT = "[%(asctime)s] %(funcName)s: %(message)s"
logging.basicConfig(format=FORMAT)
class QtTest(QtWidgets.QWidget):
def __init__(self):
super(QtTest, self).__init__()
logger.info("Class initialized")
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.setInterval(1000)
self.timer.timeout.connect(lambda: self.target_func(0))
self.timer.start()
def target_func(self, data):
logger.info("Target function called with data: {}".format(data))
time.sleep(1.5)
if self.timer.isActive():
logger.info("Timer reset")
self.timer.stop()
self.timer.timeout.disconnect()
self.func_done(data)
def func_done(self, data):
logger.info("Tasks completed for data: {}".format(data))
if data == 0:
logger.info("Setting next timer with data 1")
#I have tried the following as well
#self.timer.timeout.disconnect()
#self.timer.stop()
self.timer.setInterval(1000)
self.timer.timeout.connect(lambda: self.target_func(1))
self.timer.start()
def init_app():
app = QtWidgets.QApplication([])
gui = QtTest()
gui.show()
app.exec_()
if __name__ == "__main__":
print("QT app running")
init_app()
test = QtTest()
这是测试的输出代码:
QT app running
[2024-07-30 22:44:53,204] __init__: Class initialized
[2024-07-30 22:44:54,255] target_func: Target function called with data: 0
[2024-07-30 22:44:55,757] func_done: Tasks completed for data: 0
[2024-07-30 22:44:55,757] func_done: Setting next timer with data 1
[2024-07-30 22:44:56,808] target_func: Target function called with data: 0
[2024-07-30 22:44:58,309] func_done: Tasks completed for data: 0
[2024-07-30 22:44:58,309] func_done: Setting next timer with data 1
[2024-07-30 22:44:58,309] target_func: Target function called with data: 1
[2024-07-30 22:44:59,810] target_func: Timer reset
[2024-07-30 22:44:59,811] func_done: Tasks completed for data: 1
我期望的是,一旦计时器设置为新值 1,目标函数也会打印 1。
我也尝试过使用 lambda 的默认值,如 What do lambda 中所述函数闭包捕获? 具有相同的结果:
self.timer.timeout.connect(lambda x=1: self.target_func(x))
问题在于你的 lambda 函数的方式正在捕获
data
变量。在 Python 中,lambda 函数会捕获它们定义时在其周围范围内的变量的值。它们不会像你期望的那样在每次调用时都获取变量的当前值。
因此,你的 lambda 函数
lambda: self.target_func(data)
总是使用
data
的初始值,即 0。
要解决此问题,你需要在 lambda 函数本身内部创建一个新的变量范围,该范围可以存储
data
的当前值。可以通过将
data
作为默认参数传递给 lambda 函数来实现:
self.timer.timeout.connect(lambda data=data: self.target_func(data))
在此代码中,
data=data
为 lambda 函数创建了一个名为
data
的新参数,该参数的默认值设置为
data
的当前值。这意味着每次调用 lambda 函数时,它都会使用作为默认参数传递的
data
的值,而不是使用在周围范围内捕获的值。
以下是已更正的代码:
from PySide2.QtCore import QTimer
from PySide2 import QtWidgets
import time
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
FORMAT = "[%(asctime)s] %(funcName)s: %(message)s"
logging.basicConfig(format=FORMAT)
class QtTest(QtWidgets.QWidget):
def __init__(self):
super(QtTest, self).__init__()
logger.info("Class initialized")
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.setInterval(1000)
self.timer.timeout.connect(lambda data=0: self.target_func(data))
self.timer.start()
def target_func(self, data):
logger.info("Target function called with data: {}".format(data))
time.sleep(1.5)
if self.timer.isActive():
logger.info("Timer reset")
self.timer.stop()
self.timer.timeout.disconnect()
self.func_done(data)
def func_done(self, data):
logger.info("Tasks completed for data: {}".format(data))
if data == 0:
logger.info("Setting next timer with data 1")
self.timer.setInterval(1000)
self.timer.timeout.connect(lambda data=1: self.target_func(data))
self.timer.start()
def init_app():
app = QtWidgets.QApplication([])
gui = QtTest()
gui.show()
app.exec_()
if __name__ == "__main__":
print("QT app running")
init_app()
test = QtTest()
此代码现在应该按预期工作,并在每次调用计时器时使用
data
的正确值。