Python中类方法访问方式不同导致id值差异的原因解析
类方法不同访问方式下id值差异的原因分析
问题现象
测试Python类方法时发现id值异常:定义MyClass类并创建实例obj1、obj2后,分别通过类和实例访问类方法classmethod,打印其id值,结果显示不同访问方式下id存在差异;且Python 3.9.12、3.10.7版本与3.8.10版本的输出表现不同。
测试代码
class MyClass: def method(self): return 'instance method called', self @classmethod def classmethod(cls): return 'class method called', cls @staticmethod def staticmethod(): return 'static method called' obj1 = MyClass() obj2 = MyClass() print(id(MyClass.classmethod), "MyClass") print(id(obj1.classmethod), "obj1") print(id(obj2.classmethod), "obj2") print("------------------------") print(id(MyClass.classmethod), MyClass.classmethod) print(id(obj1.classmethod), obj1.classmethod) print(id(obj2.classmethod), obj2.classmethod)
输出结果
140600363181376 MyClass 140600363181568 Obj1 140600363181376 Obj2 ------------------------ 140600363181568 <bound method MyClass.classmethod of <class '__main__.MyClass'>> 140600363181568 <bound method MyClass.classmethod of <class '__main__.MyClass'>> 140600363181568 <bound method MyClass.classmethod of <class '__main__.MyClass'>>
原因解析
1. 类方法的Descriptor特性
@classmethod装饰器创建的是一个descriptor对象,它的__get__方法会根据访问场景返回不同的对象:
- 通过类访问(
MyClass.classmethod):- 在Python 3.8及更早版本中,直接返回
classmethod实例本身; - 在Python 3.9+版本中,会返回一个绑定到当前类的
bound method对象(和通过实例访问返回的对象类型一致)。
- 在Python 3.8及更早版本中,直接返回
- 通过实例访问(
obj1.classmethod):无论哪个Python版本,都会触发__get__方法,返回绑定到类的bound method对象(类方法绑定的是类,而非实例)。
2. 内存地址(id)的复用机制
Python中id()返回的是对象在内存中的唯一地址,但当一个对象被垃圾回收(不再被引用)后,新创建的对象可能复用这个地址。你输出中两次打印的id不一致,就是因为:
- 第一次打印时,临时创建的对象(比如
classmethod实例或临时bound method)在打印后就被销毁,内存地址被释放; - 第二次打印时,新创建的
bound method对象复用了之前释放的地址,导致所有id看起来相同。
3. 如何判断是否为同一方法
不要依赖id()或直接用is判断bound method对象是否相同,因为每次访问都可能创建新的bound method实例。正确的做法是查看方法的__func__属性,它指向原始的函数对象:
print(MyClass.classmethod.__func__ is obj1.classmethod.__func__) # 输出 True print(MyClass.classmethod.__func__ is obj2.classmethod.__func__) # 输出 True
所有访问方式得到的类方法,其__func__都指向同一个原始函数,这才是它们本质相同的证明。
内容的提问来源于stack exchange,提问作者Aayush Shah
相关产品推荐
相关产品推荐

