如何从外部访问类内的self对象

2024-09-24 04:27:39 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在编写一个使用QtGUI的应用程序,现在正在尝试对其进行多线程处理(第一次学习)。一个较长的任务最终将包含在工作线程中,但输出日志需要在执行过程中写入。GUI类具有将这些日志输出到纯文本小部件的方法。到目前为止,一切都在GUI类中运行

我目前有以下代码(包括为简洁起见的重要部分):

class Worker(QRunnable):
    @pyqtSlot()
    def run(self):
        Ui.logentry(self, "Test")
    
class Ui(QtWidgets.QMainWindow):
    def __init__(self):
        super(Ui, self).__init__()
        uic.loadUi(resourcepath('./pycdra.ui'), self)
        
        self.outputlog = self.findChild(QtWidgets.QPlainTextEdit, 'outputlog')
        self.button = self.findChild(QtWidgets.QPushButton, 'button')
        self.button.clicked.connect(self.Button)
        self.threadpool = QThreadPool()
        self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())
        
    def Button(self):
        worker = Worker()
        self.threadpool.start(worker)
    
    def logentry(self, returntext):
        self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))
        self.outputlog.repaint()
        
    def timestamp(self):
        import datetime
        ts = datetime.datetime.now(tz=None).strftime("%Y-%m-%d %H:%M:%S"))
        return ts
        
def applic():
    app = QApplication(sys.argv)
    window = Ui()
    window.show()
    sys.exit(app.exec_())

applic()

当我试着运行这个程序时,GUI可以完美地加载,但是在按下button时,WorkerUi.logentry部分返回错误:AttributeError: 'Worker' object has no attribute 'outputlog'

我试图使logentry()timestamp()成为全局的,这样它们就可以被这两个类访问,但最接近Ui.self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))行的是'Ui' object has no attribute 'self'

我错过了什么?我让它们全球化是正确的,还是有其他方法可以做到这一点


Tags: 方法selfuidatetimedefguibuttontimestamp
1条回答
网友
1楼 · 发布于 2024-09-24 04:27:39

您的代码存在各种问题。我将试着按照重要性的顺序来处理它们

  1. logentryUi的一个实例方法,这意味着它需要一个Ui实例才能运行;您试图实现的目标永远不会起作用,因为self引用了Worker的一个实例,它显然没有任何outputlog属性(这是logentry所期望的)。记住:self参数不只是为了好玩,它总是指调用函数的对象(实例,例如实例方法)self.logentry表示“使用self作为第一个参数调用函数logentry。因为在您的例子中,self引用了Ui的一个实例,这解释了您的主要问题,因为该实例没有该属性

  2. None正是它的名字所说的:nothingtimestamp函数将抛出另一个AttributeError,因为在None中没有strftime属性。这是datetime对象的一部分,因此您必须获得一个now对象,然后对其调用strftime函数。在任何情况下,tz参数都需要一个参数tzinfo子类

  3. pyqtSlot仅适用于从QObject(如QWidget)继承的Qt类。QRunnable不是这些类中的一个。您可以在每个类的文档标题中检查整个继承树:如果有“继承:“字段”,直到“没有”。如果您在某个点获得了QObjt继承,则可以使用该对象的槽和信号,否则不必考虑。QWIDGET也继承了QObjt; >所有EEM> QT窗口小部件继承自QWIDGET;Qt对象继承QQObjt,但小部件。

  4. 即使忽略以上所有内容,从外部线程(包括QRunnable对象)访问UI元素总是被禁止的,虽然理论上是可以的(但不可靠)获取它们的属性,尝试设置它们很可能会导致崩溃;为了更改UI元素中的某些内容,必须使用signals命令。请注意,“访问”还包括创建,并且总是导致崩溃

  5. 调用repaint是解决上述问题的常见(也是错误的)尝试;这是不必要的,因为正确地设置小部件属性(如使用appendPlainText())已经会导致单独的计划重新绘制;很少有情况下repaint实际上是必要的,经验法则是,如果你调用它,你可能不知道你在做什么或者为什么要这么做。在任何情况下,调用^{}总是首选的,而且无论如何都必须从主UI线程调用它

  6. 很少需要在函数中使用导入,因为导入语句应该始终在脚本的最开始处;虽然有些情况下可以(或应该)在以后或在特定函数中进行导入,但在可能经常调用的函数中进行导入会使在那里使用它们完全没有意义是标准库的一部分,因此按需导入它几乎不会影响性能(特别是考虑到它的“性能权重”与类似Qt的大型库相比)

  7. 当从.ui文件(或pyuic生成的文件)加载ui时,PyQt alread创建所有小部件作为实例属性,因此不需要findChild。不幸的是,有很多教程建议使用这种方法,它们完全是错误的。您已经可以作为^{访问这些小部件了和self.button之后的uic.loadUi

  8. 函数名(如变量和属性)应始终以小写字母开头,因为只有类和常量应以大写字母开头(参见官方Style Guide for Python Code)。此外,对象名应始终解释这些对象的功能(参见“self documenting代码“):执行“操作”的函数应该有一个动词;如果它被命名为“按钮”,它不会告诉我它将进行一些处理,这不是一件很好的事情

  9. “main”函数(如applic)通常在公共if __name__ == '__main__':块中有意义,它确保在文件被导入而不是直接运行时不会调用该函数(请参见this answer和相关的question


由于QRunnable不是从QObject继承的,我们可以创建一个QObject子类作为信号“代理”,并使其成为QRunnable实例的成员。然后每次创建新的Worker对象时,我们都必须连接到该信号

以下是基于以上几点的代码修订版

import datetime
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi

class WorkerSignals(QObject):
    mySignal = pyqtSignal(str)


class Worker(QRunnable):
    def __init__(self):
        super().__init__()
        self.signalProxy = WorkerSignals()
        self.mySignal = self.signalProxy.mySignal

    def run(self):
        self.mySignal.emit("Test")


class Ui(QMainWindow):
    def __init__(self):
        super(Ui, self).__init__()
        loadUi('./pycdra.ui', self)
        
        self.button.clicked.connect(self.startWorker)
        
        self.threadpool = QThreadPool()
        self.logentry("Available threads: %d" % self.threadpool.maxThreadCount())

    def startWorker(self):
        worker = Worker()
        worker.mySignal.connect(self.logentry)
        self.threadpool.start(worker)

    def logentry(self, returntext):
        self.outputlog.appendPlainText(self.timestamp() + " " + str(returntext))

    def timestamp(self):
        ts = datetime.datetime.now()
        return ts.strftime("%Y-%m-%d %H:%M:%S")


def applic():
    import sys
    app = QApplication(sys.argv)
    window = Ui()
    window.show()
    sys.exit(app.exec_())

if __name__ == "__main__":
    applic()

我建议您仔细研究提供的代码及其与您的代码的差异,然后对上面给出的链接和以下主题进行仔细、耐心的研究:

  • 类和实例,以及self的含义
  • python类型(包括None
  • 什么是event driven programming以及它与图形界面的关系
  • 最重要的Qt类QObjectQWidget,以及它们的所有属性和函数(是的,它们是很多
  • 一般PyQt相关主题 (最重要的是,信号/插槽和属性)
  • 代码样式和良好实践

相关问题 更多 >