以代码为例
class Person:
name = None
age = None
# 分析对象作为参数传递到函数/方法的机制
def f1(person):
print(f"2. person的地址:{id(person)}")
person.name = "james"
person.age += 1
p1 = Person()
p1.name = "jordan"
p1.age = 21
print(f"1. p1的地址: {id(p1)} ,p1.name: {p1.name} ,p1.age: {p1.age}")
f1(p1)
print(f"3. p1的地址: {id(p1)} ,p1.name: {p1.name} ,p1.age: {p1.age}")
代码理论分析
我们先创建了一个Person类,类中有两个属性,分别为name属性和age属性,随后在Person类外创建了一个 f1 函数。
随后通过执行下述代码创建了一个对象p1,
p1 = Person()
相当于在我们的主栈区有了p1对象,这个p1指向数据区的一个空间,假设该空间的地址为0x1122。
接下来执行
p1.name = "jordan"
p1.age = 21
代码,即我们给p1对象的name属性赋值为“jordan”,给age属性赋值为21,那么我们的内存图应如下所示。
接下来,执行
print(f"1. p1的地址: {id(p1)} ,p1.name: {p1.name} ,p1.age: {p1.age}")
语句,根据上面的内存图可知,p1对象的id为0x1122,p1对象的name属性值为jordan,p1对象的age属性值为21。输出控制台得到如下所示。
紧接着,程序执行
f1(p1)
代码,调用 f1 函数,这个时候它相当于开了一个新栈,此时将p1作为实际参数赋给形式参数person,那么在新栈中,相当于有一个person对象,那么该person对象指向数据区的哪个空间呢?
我们知道,对象传参本质是传递引用,就相当于person对象也指向0x1122这个数据空间,如下图所示。
接下来,执行f1函数中的print语句,该语句要输出person的地址,这个person地址与前面的p1对象地址是一样的,则该语句输出结果为:0x1122。
接下来,执行f1函数中的下面两条语句:
person.name = "james"
person.age += 1
通过person对象修改name、age属性的值,name属性的值原先是jordan,修改后为james,age属性的值原先是21,修改后为22,过程如下图所示。
上述流程执行结束后,程序返回主栈继续执行剩余的代码,即最后一行代码,如下所示。
print(f"3. p1的地址: {id(p1)} ,p1.name: {p1.name} ,p1.age: {p1.age}")
我们由内存图可以得知,p1对象的id值没有变化,仍然是0x1122,但p1的name属性、age属性均被改变,p1.name为james,p1.age为22。 程序最终输出结果如下图所示。
我们运行我们的程序,得到如下结果:
1. p1的地址: 1769002692360 ,p1.name: jordan ,p1.age: 21
2. person的地址:1769002692360
3. p1的地址: 1769002692360 ,p1.name: james ,p1.age: 22
注意:地址不唯一,但地址都是一样的。
编译器验证
接下来,我们使用实际的编译器演示上述流程,来验证我们的分析是否正确。代码如下所示,11行至27行,我们在第21行打上断点,用以测试。
开始调试,点击调试按钮,程序执行到第20行停止,此时还没有执行到第21行,故此时p1对象是不存在的。函数f1位于的内存地址是:<function f1 at 0x0000016ED4E1F048>
接下来我们Step Into或按F7逐步调试。 得到如下结果,我们发现,此时p1对象被创建,地址是:p1: <__main__.Person object at 0x0000016ED4E16DC8>
观察变量区:
p1对象被创建,p1的两个属性age和name值均为None。
再按两次Step Into,为p1对象的name属性及age属性赋值,得到如下结果,p1对象的name属性被赋值为jordan,age属性被赋值为21。
再按下Step Into,程序将会执行第25行的打印语句,程序输出:
再按下Step Into,程序将会执行第26行语句 ,进入到函数f1中(进入新栈),注意此时的person对象地址为:<__main__.Person object at 0x0000016ED4E16DC8>,与前面的p1对象地址一致,如下图所示。
观察数据区,person对象的name属性为jordan,age属性为21。
再按下Step Into,程序将执行第17行,打印person对象的地址,如下图所示。
再按下Step Into,程序执行第18行代码,借助person对象修改name属性为james,如下图所示。
再按下Step Into,程序执行第19行代码,借助person对象修改age属性为22,并返回主栈,如下图所示。
最后,程序执行最后一行代码,即第27行代码,打印p1对象的地址,name属性,age属性。最终结果如下图所示。
至此,整个执行流程到此结束,我们发现,编译器验证步骤与我们分析的一致。
总结
当对象作为参数在函数或者方法中进行传递的时候(函数或方法中会修改对象的属性值),那么最终会影响到对象的属性值。