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

Posted

技术标签:

【中文标题】如何从外部访问类内的自我对象【英文标题】:How to access self object within class from outside 【发布时间】:2021-07-24 07:11:44 【问题描述】:

我正在编写一个使用 Qt GUI 的应用程序,现在正在尝试对其进行多线程处理(第一次学习)。更长的任务最终将包含在工作线程中,但输出日志将需要在执行过程中写入。 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'

我错过了什么?我是否正确地使它们全球化或有其他方法可以做到这一点?

【问题讨论】:

在此期间可能发生了一些变化,但至少在 Python 3.7 上,上述代码会导致第一个装饰器出现语法错误。而且,无论如何,pyqtSlot 装饰器仅适用于 QObject 子类,而 QRunnable 则不然。 我正试图回答你,但是当我浏览你的代码时,我发现越来越多的问题。请确保您提供可靠的代码,该代码应为minimal and reproducible。 糟糕,这是我的事。代码在一个封闭的网络上,所以我不得不手动重新输入它并在 Worker 类中忘记了def run(self):。编辑问题以包括它。关于装饰器,它在我正在关注的教程示例中,我认为它很重要。如果不是,它就消失了。 不要删除属于您的问题的部分。只需确保您的代码正确且准确地重现它即可。 【参考方案1】:

您的代码存在各种问题。我将尝试按重要性顺序解决它们。

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

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

    pyqtSlot 仅适用于继承自 QObject 的 Qt 类(例如 QWidget)。 QRunnable 不是这些类之一。您可以在每个类的文档标题中检查整个继承树:如果有“Inherits:”字段,则向上直到没有。如果您在某个时候获得了 QObject 继承,那么您可以为该对象使用槽和信号,否则不能。考虑一下: QWidget 也继承自 QObject; 所有 Qt 小部件继承自 QWidget;有 Qt 类继承自 QObject,但 不是 小部件。

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

    调用repaint 是解决上述问题的常见(也是错误的)尝试;这是不必要的,因为正确设置小部件属性(如使用appendPlainText())已经导致自己计划重绘;实际上很少需要repaint 的情况,经验法则是,如果您调用它,您可能不知道自己在做什么或为什么这样做。在任何情况下,总是首选调用update(),并且无论如何都必须始终从主 UI 线程调用它。

    很少需要在函数中使用导入,因为导入语句应始终位于脚本的开头;虽然在某些情况下可以(或应该)稍后或在特定函数中完成导入,但在可能经常调用的函数中执行它们会使在那里使用它们完全没有意义。此外,datetime 是标准库的一部分,因此按需导入它几乎不会影响性能(尤其是考虑到它的“性能权重”与 Qt 之类的 big 库相比)。

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

    函数名称(如变量和属性)应始终以小写字母开头,因为只有类和常量应以大写字母开头(参见官方Style Guide for Python Code)。此外,对象名称应始终解释这些对象的作用(请参阅“自记录代码”):执行“动作”的函数应具有动词;如果它被命名为“Button”,它不会告诉我它会做一些处理,这不是一件好事。

    “主”函数(如您的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 类,QObject 和 QWidget,以及它们的所有属性和功能(是的,它们很多); 一般PyQt相关话题 (最重要的是,信号/槽和属性); 代码样式和良好做法;

【讨论】:

以上是关于如何从外部访问类内的自我对象的主要内容,如果未能解决你的问题,请参考以下文章

php中的const和static

Java 修饰符

Java修饰符

Java内部类

为啥内部类的private变量可被外部类直接访问

从外部引用设置属性上的 .attributedText(可通过 NIB 加载的 UIView 内的插座访问)似乎失败