阅读了https://www.toptal.com/python/python-class-attributes-an-overly-thorough-guide个帖子后,我认为我掌握了class attributes
和instance attributes
之间的区别。但是,仍然有一种我无法解释的怪异现象:PyQt中pyqtSignal()
的行为。
我了解到,类属性存在于类的名称空间中。如果您通过实例访问此类变量(例如class_var
),Python将首先在实例的名称空间中查找。在那找不到它,Python会查看类的名称空间。
让我们看一下类属性,它是一个简单的整数。您应该通过类本身来访问它,但是也可以通过其实例之一来访问它:
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
下图说明了会发生什么:
在前面的示例中,赋值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
进行了突变,但是突变发生在实际的类变量上,这对于该类的所有实例都是通用的:
请考虑以下代码示例。类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
类变量连接到显示“一个”的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()
上调用signal
方法。首先,我们通过signal
到达obj_01
,然后通过obj_02
。 emit()
方法的行为有所不同,具体取决于我们如何达到signal
属性!
obj_01.signal.emit() # prints "one"
obj_02.signal.emit() # prints "two"
我对您的第一个问题是:为什么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()
会引发错误?我只是在访问类变量,因为它应该发生...