Python类实例方法内存地址(id返回值)频繁变化原因咨询
问题现象
- 调试包含多个私有内部方法的大型Python模块时,通过VSCode调试器观察到:单步执行任意代码(包括与这些内部方法无关的代码段)时,所有内部方法的内存地址都会发生改变,最初曾怀疑代码存在严重Bug。
- 经过排查,脱离VSCode、不依赖调试器的纯命令行环境下可复现该问题,复现代码如下:
class MyClass(): def __init__(self): self._internal() def _internal(self): for i in range(10): print(i, f'id={id(self._internal)}') x = MyClass()
- 多次运行上述测试代码时,
_internal方法的id值有时保持静态,有时会在for循环执行过程中出现地址反复变动的情况,命令行直接运行的典型输出如下:
> "\Program Files\Python310"\python internal_method.py 0 id=2298435929536 1 id=2298435929728 2 id=2298435929536 3 id=2298435929728 4 id=2298435929536 5 id=2298435929728 6 id=2298435929536 7 id=2298435929728 8 id=2298435929536 9 id=2298435929728
- 在VSCode调试器中单步执行该循环时,地址变化会更加无规律,调试启动后的典型输出如下:
> c: && cd c:\Code\#Scrap && cmd /C ""C:\Program Files\Python310\python.exe" c:\Users\blah\.vscode\extensions\ms-python.python-2022.8.0\pythonFiles\lib\python\debugpy\launcher 50429 -- c:\Code\#Scrap\internal_method.py " 0 id=2341889160512 1 id=2341898208576 2 id=2341886959552 3 id=2341898111680 4 id=2341898210112 5 id=2341850994176 6 id=2341898298048 7 id=2341889399040 8 id=2341850998208 9 id=2341898172800
根本成因
该现象是CPython的标准实现机制导致,既不是代码Bug,也不是调试器故障:
- “函数内存地址固定”的认知仅适用于类定义完成后,类本身持有的原始函数对象(即
MyClass._internal),该对象的内存地址在类加载完成后不会发生变化。 - 当通过实例访问方法(即
self._internal)时,Python的方法描述符机制会在每次属性访问时临时生成一个新的绑定方法(bound method)对象,该对象会同时绑定当前实例引用与原始函数引用,实现调用时自动传入self参数的逻辑。 - 代码中每次执行
id(self._internal)时,都会触发一次属性访问、生成一个全新的绑定方法对象;上一次访问生成的绑定方法因为没有被任何变量持有引用,会立刻被垃圾回收,其占用的内存地址会被内存分配器回收复用。- 命令行运行时两个id交替出现,是因为该场景下内存分配逻辑简单,刚好反复复用两个固定内存块存放新生成的绑定方法。
- VSCode调试环境下,调试器会注入大量额外运行时对象、持有更多临时引用,内存分配的随机性更强,因此观测到的id值变化更无规律。
验证方式
运行以下代码可直接验证上述逻辑:
class MyClass(): def _internal(self): pass obj = MyClass() # 类持有的原始函数id始终固定 print(f"类属性访问函数id1: {id(MyClass._internal)}") print(f"类属性访问函数id2: {id(MyClass._internal)}") # 实例访问每次生成新的绑定方法 m1 = obj._internal m2 = obj._internal print(f"实例绑定方法id1: {id(m1)}") print(f"实例绑定方法id2: {id(m2)}") print(f"两个绑定方法为同一对象: {m1 is m2}")
运行结果会显示类上的函数两次访问id完全一致,而两次获取的实例绑定方法id不同,is判断返回False。如果删除m1、m2的引用触发垃圾回收,再次访问obj._internal时,新生成的绑定方法会复用之前释放的内存地址,与之前观测到的id跳变现象完全吻合。
日常开发中不要依赖实例方法的id值做身份判断、缓存键等逻辑即可,该机制不会影响正常的方法调用功能。
内容的提问来源于stack exchange,提问作者mrkbutty
相关产品推荐
相关产品推荐

