Python对象作为参数传递到函数/方法的机制

以代码为例 

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属性。最终结果如下图所示。 

 至此,整个执行流程到此结束,我们发现,编译器验证步骤与我们分析的一致。

总结

        当对象作为参数在函数或者方法中进行传递的时候(函数或方法中会修改对象的属性值),那么最终会影响到对象的属性值。

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值