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
相关产品推荐
相关产品推荐

