python - 为什么 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()
抛出错误?我只是在访问应该发生的类变量...
解决方案
推荐阅读
- apache-superset - 在 Superset 中从 http 更改为 https
- java - 将 GZIP 内容提取到字符串以获取大数据字节 java
- java - 当找到第一个结果时,RxJava 中断并行执行
- php - 未创建错误会话:此版本的 ChromeDriver 仅支持 Chrome 版本 80,安装的 chromedriver 为 85
- angular - 从 Angular 应用程序调用第三方支付 URL
- google-classroom - 如何在您的网站中添加按钮以加入 Google 课堂?
- javascript - 如何使用 next-auth 获取额外的范围数据?
- algorithm - 给定一个数组并将它们分组为可能的总和
- python - globals() 函数在 python 中有奇怪的行为
- python - 如何将输入限制为一系列数字?