
本文深入探讨python中类变量和实例变量的唯一性问题,特别是在`qobject`和`signal`的上下文。通过分析`signal`的实例绑定行为与普通类属性的共享行为差异,揭示了python描述符协议的作用。文章强调,若需确保每个实例拥有独立的属性对象,最pythonic且推荐的做法是在类的`__init__`方法中进行初始化,而非将其定义为类属性。
在Python中,当我们在类定义内部声明一个变量时,它通常被称为类属性。类属性默认由该类的所有实例共享。然而,某些特殊类型的类属性,如PySide2.QtCore.Signal,表现出与普通类属性不同的行为,它们似乎为每个实例提供了唯一的对象。
考虑以下示例代码:
from PySide2.QtCore import QObject, Signal
class MyClass:
    pass
class PSClass(QObject):
    onRequest = Signal(str)
    onMySignal = MyClass() # 这是一个普通的类属性
    def __init__(self):
        super().__init__()
        self.onRequest.connect(self.printSomething)
    def send(self, message:str):
        self.onRequest.emit(message)
    def printSomething(self, data:str):
        print(data)
sample_01 = PSClass()
sample_02 = PSClass()
sample_01.send(f"Signal ID:{id(sample_01.onRequest)}, MyClass ID:{id(sample_01.onMySignal)}")
sample_02.send(f"Signal ID:{id(sample_02.onRequest)}, MyClass ID:{id(sample_02.onMySignal)}")运行上述代码,输出结果可能如下:
Signal ID:56331376, MyClass ID:15825232 Signal ID:56331392, MyClass ID:15825232
从输出中可以看出,sample_01.onRequest和sample_02.onRequest的内存地址(ID)是不同的,这意味着它们是两个独立的Signal对象。然而,sample_01.onMySignal和sample_02.onMySignal的ID却是相同的,表明它们引用了同一个MyClass对象。这种差异引出了一个核心问题:为什么Signal属性表现出实例唯一性,而普通的MyClass属性则不然?以及如何使MyClass也具有实例唯一性?
立即学习“Python免费学习笔记(深入)”;
要理解上述行为,首先需要明确Python中类属性和实例属性的基本概念:
在我们的示例中,onMySignal = MyClass()被定义为类属性。因此,PSClass的所有实例(sample_01和sample_02)都共享同一个MyClass对象,所以它们的ID是相同的。
Signal之所以能够为每个实例提供唯一的对象,是因为它利用了Python的描述符协议 (Descriptor Protocol)。描述符是实现了特定方法(如__get__, __set__, __delete__)的Python对象。当一个类属性是描述符时,它的访问行为会被这些特殊方法所控制。
PySide2.QtCore.Signal(以及其他一些内置类型,如方法)几乎肯定实现了描述符协议。这意味着当通过实例(例如sample_01.onRequest)访问onRequest时,Signal的__get__方法会被调用。这个__get__方法能够返回一个与当前实例(sample_01)绑定的、独特的Signal对象。这就是为什么即使onRequest被定义为类属性,但每个实例访问它时,都能得到一个独立的、实例绑定的对象。
Python的方法也是描述符的一个典型例子。当你通过实例调用一个方法时(例如sample_01.send()),方法对象会通过描述符协议自动将sample_01绑定为第一个参数self。
对于大多数不需要实现复杂行为的普通对象,若要确保每个实例都拥有独立的属性对象,最直接、最Pythonic且最推荐的方法是在类的构造函数__init__中将其定义为实例属性。
通过将onMySignal的初始化从类定义体中移到__init__方法中,我们可以确保每次创建PSClass的新实例时,都会创建一个全新的MyClass对象并绑定到该实例上。
以下是修改后的PSClass定义:
from PySide2.QtCore import QObject, Signal
class MyClass:
    pass
class PSClass(QObject):
    onRequest = Signal(str) # Signal 仍然是类属性,因为它实现了描述符协议
    def __init__(self):
        super().__init__()
        self.onMySignal = MyClass() # 现在 onMySignal 是一个实例属性,每个实例都拥有独立的副本
        self.onRequest.connect(self.printSomething)
    def send(self, message:str):
        self.onRequest.emit(message)
    def printSomething(self, data:str):
        print(data)
sample_01 = PSClass()
sample_02 = PSClass()
sample_01.send(f"Signal ID:{id(sample_01.onRequest)}, MyClass ID:{id(sample_01.onMySignal)}")
sample_02.send(f"Signal ID:{id(sample_02.onRequest)}, MyClass ID:{id(sample_02.onMySignal)}")现在运行这段代码,输出将变为:
Signal ID:56331376, MyClass ID:15825232 Signal ID:56331392, MyClass ID:15825248 # MyClass ID 现在也不同了
可以看到,onMySignal的ID在sample_01和sample_02之间也变得不同了,这证明每个实例现在都有其独立的MyClass对象。
虽然理论上可以通过为MyClass实现描述符协议来达到类似Signal的效果,但对于大多数场景而言,这种做法通常不被推荐,原因如下:
理解Python中类属性、实例属性以及描述符协议是编写健壮和高效代码的关键。
对于需要实现实例级唯一性的普通对象,最佳实践始终是在类的__init__方法中进行初始化,以保证代码的简洁性、可读性和Pythonic风格。只有在需要实现复杂且非标准的属性访问逻辑时,才应考虑使用描述符协议。
以上就是Python中类变量与实例变量的唯一性管理:理解描述符协议与实例属性的最佳实践的详细内容,更多请关注php中文网其它相关文章!
 
                        
                        每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
 
                Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号