为什么pyqtSignal()是类属性,而实际上却是实例属性呢?

时间:2018-10-03 12:30:02

标签: python python-3.x pyqt pyqt5 signals-slots

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


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 new 属性。实际的类变量保持不变:

    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

下图说明了会发生什么:

enter image description here

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()对象重新分配给变量,我只是对其进行了突变。我通过实例obj_01进行了突变,但是突变发生在实际的类变量上,这对于该类的所有实例都是通用的: enter image description here


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()

enter image description here

第2步
接下来,我们将signal类变量连接到显示“一个”的lambda函数。我们通过signal访问obj_01类变量,但这没关系,因为我们不分配新值,而只是对其进行突变:

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

enter image description here

第3步
最后,我们将signal类变量连接到显示“两个”的lambda函数。这次我们通过signal访问obj_02类变量,但这又没关系:

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

enter image description here

步骤4
最后,我们在emit()上调用signal方法。首先,我们通过signal到达obj_01,然后通过obj_02 emit()方法的行为有所不同,具体取决于我们如何达到signal属性!

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

enter image description here

3。我的问题

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

一个可能的解释可能是obj_01obj_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()会引发错误?我只是在访问类变量,因为它应该发生...

0 个答案:

没有答案