You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python类函数地址异常行为咨询:涉及copy.deepcopy()场景

Python 3.10中方法绑定与深拷贝的行为解析

测试代码

import copy

class class1:
    def __init__(self):
        self.var = None
        print('class1.__init__() at       %s'%str(hex(id(self))))

    def run(self):
        self.var = 1
        print("func1() address            %s->%s" % (str(hex(id(self))), str(hex(id(self.func1)))))
        self.func1(' directly ')
        dict1 = {
            "func1": self.func1,
        }
        print("func1() address in dict1   %s->%s" % (str(hex(id(self))), str(hex(id(dict1['func1'])))))
        self.var = 2
        dict1['func1']('via dict1 ')
        dict2 = copy.deepcopy(dict1)
        print("func1() address in dict2   %s->%s" % (str(hex(id(self))), str(hex(id(dict2['func1'])))))
        self.var = 3
        dict2['func1']('via dict2 ')

    def func1(self, context):
        print('called func1(%s)   %s->%s, var=%i'%(context, str(hex(id(self))), str(hex(id(self.func1))), self.var))
        print('called func1(%s)   %s->%s, var=%i'%(context, str(hex(id(self))), str(hex(id(self.func1))), self.var))

cl1 = class1()
cl1.run()

运行结果

class1.__init__() at       0x1c24355bf10
func1() address            0x1c24355bf10->0x1c24354bb40
called func1( directly )   0x1c24355bf10->0x1c24354bbc0, var=1
called func1( directly )   0x1c24355bf10->0x1c24354bb40, var=1
func1() address in dict1   0x1c24355bf10->0x1c24354bb40
called func1(via dict1 )   0x1c24355bf10->0x1c2435640c0, var=2
called func1(via dict1 )   0x1c24355bf10->0x1c2435640c0, var=2
func1() address in dict2   0x1c24355bf10->0x1c2435640c0
called func1(via dict2 )   0x1c24355bbe0->0x1c243564180, var=2
called func1(via dict2 )   0x1c24355bbe0->0x1c243564180, var=2

疑问点

  • 部分场景下通过id()获取函数地址得到两个不同值,但调用时确为同一类的同一函数;
  • 字典中存储的函数地址与调用该函数时获取的地址不一致;
  • 对字典执行copy.deepcopy()后,新字典中函数地址与第2点调用时的地址相同,但调用该函数时self对象地址不同,且self.var保留深拷贝前的状态。

问题解析

1. 为什么id()返回不同值但实际是同一函数?

Python里的实例方法不是直接存在于实例上的——当你访问self.func1时,Python会动态生成一个绑定方法对象,这个对象把实例self和类里定义的原始函数class1.func1绑定在一起。每次访问self.func1都可能生成一个新的绑定方法实例,所以id()会不一样,但这些绑定方法本质上都是指向同一个类方法,调用时都会执行class1中定义的func1逻辑。

你看到的两次id()差异,只是两次生成的绑定方法对象是不同的容器,但它们包裹的核心函数是同一个。

2. 字典中存储的函数地址与调用时不一致?

当你把self.func1存入字典时,存的是当时生成的一个绑定方法对象。但在调用dict1['func1']()的过程中,Python内部可能会重新生成绑定方法(或者因为绑定方法的临时缓存机制),导致你在函数内部再次获取self.func1时得到了新的绑定方法对象,所以id()不同。但不管怎么变,都是绑定到原实例的同一个类方法,功能不受影响。

3. 深拷贝后self地址变了,var保留旧值?

copy.deepcopy()在处理绑定方法对象时,会把绑定的实例也一起深拷贝。也就是说,dict2['func1']绑定的是原实例cl1的一个拷贝副本,而不是原实例本身。

深拷贝发生在self.var=2之后、self.var=3之前,所以拷贝出来的实例的var值被固定为2。后续修改原实例的var为3,不会影响这个拷贝实例的var,这就是为什么调用dict2['func1']时self地址变了,且var还是2。


内容的提问来源于stack exchange,提问作者Yury Spiridonov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 23:20:33