首页 > 解决方案 > 为什么 pyqtSignal() 是一个类属性,而它实际上是一个实例属性?

问题描述

阅读帖子https://www.toptal.com/python/python-class-attributes-an-overly-thorough-guideclass attributes后,我想我掌握了和之间的区别instance attributes。但是仍然有一个我无法解释的奇怪现象:pyqtSignal()PyQt 中的行为。

 

1.关于类属性

我了解到类属性存在于类的命名空间中。如果您通过实例访问此类变量 - 例如class_var- 通过实例,Python 将首先在实例的命名空间中查找。在那里找不到它,Python 会查看该类的命名空间。

1.1 原始类属性

让我们看一个类属性,它是一个简单的整数。您应该通过类本身访问它,但您也可以通过它的一个实例来访问它:

class MyClass(object):
    class_var = 1

    def __init__(self):
        pass

if __name__ == '__main__':
    obj_01 = MyClass()
    obj_02 = MyClass()

    # Access the 'class_var' attribute
    print("obj_01.class_var  -> " + str(obj_01.class_var))   # prints 1
    print("obj_02.class_var  -> " + str(obj_02.class_var))   # prints 1
    print("MyClass.class_var -> " + str(MyClass.class_var))  # prints 1

但是,如果您class_var通过实例分配一个新值,您实际上会创建一个在该特定实例的命名空间中命名的属性。class_var实际的类变量保持不变:

    obj_01.class_var = 3
    print("obj_01.class_var  -> " + str(obj_01.class_var))   # prints 3
    print("obj_02.class_var  -> " + str(obj_02.class_var))   # prints 1
    print("MyClass.class_var -> " + str(MyClass.class_var))  # prints 1

下图说明了会发生什么:

在此处输入图像描述

1.2 可变类属性(非原始)

在前面的例子中,赋值obj_01.class_var = 3并没有触及实际的类变量——它只是为特定对象创建了一个新的实例变量。对于可变(非原始)类属性,情况变得更加复杂。考虑一下:

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def get_str_value(self):
        myStr = "(" + str(self.a) + "," + str(self.b) + ")"
        return myStr

class MyClass(object):
    class_var = Foo(1, 1)

    def __init__(self):
        pass

if __name__ == '__main__':
    obj_01 = MyClass()
    obj_02 = MyClass()

    # Access the 'class_var' attribute
    print("obj_01.class_var  -> " + obj_01.class_var.get_str_value())   # prints (1,1)
    print("obj_02.class_var  -> " + obj_02.class_var.get_str_value())   # prints (1,1)
    print("MyClass.class_var -> " + MyClass.class_var.get_str_value())  # prints (1,1)

    # Change it
    obj_01.class_var.a = 3
    print("obj_01.class_var  -> " + obj_01.class_var.get_str_value())   # prints (3,1)
    print("obj_02.class_var  -> " + obj_02.class_var.get_str_value())   # prints (3,1)
    print("MyClass.class_var -> " + MyClass.class_var.get_str_value())  # prints (3,1)

我没有Foo()为变量重新分配一个新的对象,我只是改变它。我通过 instance 进行突变obj_01,但突变发生在实际的类变量上,这对该类的所有实例都是通用的: 在此处输入图像描述

 

2. pyqtSignal() 的怪异行为

考虑以下代码示例。类EmitterTester有一个名为 的类属性signal

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class EmitterTester(QObject):
    signal = pyqtSignal()   # class attribute 'signal'
    def __init__(self):
        super(EmitterTester, self).__init__()
        pass

class MainWindow(QMainWindow):
    def __init__(self):
        '''
        Just create a window with a "START TEST" button.
        '''
        super(MainWindow, self).__init__()
        self.setGeometry(200, 200, 200, 200)
        self.setWindowTitle("Emitter tester")

        self.frame = QFrame()
        self.layout = QHBoxLayout()
        self.frame.setLayout(self.layout)
        self.setCentralWidget(self.frame)

        self.button = QPushButton()
        self.button.setText("START TEST")
        self.button.setFixedHeight(50)
        self.button.setFixedWidth(100)
        self.button.clicked.connect(self.start_test)
        self.layout.addWidget(self.button)

        self.show()

    def start_test(self):
        '''
        This is the actual test code!
        '''
        obj_01 = EmitterTester()
        obj_02 = EmitterTester()
        obj_01.signal.connect(lambda: print("one"))
        obj_02.signal.connect(lambda: print("two"))

        obj_01.signal.emit()            # prints "one"
        obj_02.signal.emit()            # prints "two"
        EmitterTester.signal.emit()     # throws error

if __name__ == '__main__':
    app = QApplication(sys.argv)
    myGUI = MainWindow()
    sys.exit(app.exec_())

因此,让我们逐步查看有问题的代码片段。

第1
步首先我们创建EmitterTest类的两个实例。名为的类变量signal尚未连接到任何东西。

obj_01 = EmitterTester()
obj_02 = EmitterTester()

在此处输入图像描述

第 2
步接下来,我们将signal类变量连接到打印“one”的 lambda 函数。我们signal通过 访问类变量obj_01,但这不重要,因为我们没有分配一个新值,而只是改变它:

obj_01.signal.connect(lambda: print("one"))

在此处输入图像描述

第 3
步最后,我们将signal类变量连接到打印“二”的 lambda 函数。这次我们通过 访问signal类变量obj_02,但同样,这无关紧要:

obj_02.signal.connect(lambda: print("one"))

在此处输入图像描述

第 4
步最后我们调用emit()on 方法signal。首先我们signal通过obj_01,然后通过obj_02。该emit()方法的行为不同,这取决于我们如何达到signal属性!

obj_01.signal.emit()            # prints "one"
obj_02.signal.emit()            # prints "two"

在此处输入图像描述

3. 我的问题

我对您的第一个问题是:为什么emit()方法的行为会有所不同,这取决于我们如何达到signal属性?

一种可能的解释是,obj_01每个人obj_02都制作了自己的类变量副本(因此他们实际上创建了自己的“实例变量”)。为了验证这个理论,我检查了对象和类的命名空间:

print(obj_01.__dict__)         # prints "{}"
print(obj_02.__dict__)         # prints "{}"
print(EmitterTester.__dict__)  # prints "{'__module__' : '__main__',
                               #              'signal' : <unbound PYQT_SIGNAL signal()>,
                               #            '__init__' : <function EmitterTester.__init__ at 0x000001DE592098C8>,
                               #             '__doc__' : None}

如您所见,该signal属性仅在类的命名空间中。对象没有自己的副本。

我的第二个问题是:为什么会EmitterTester.signal.emit()抛出错误?我只是在访问应该发生的类变量...

标签: pythonpython-3.xpyqtpyqt5signals-slots

解决方案


推荐阅读