欢迎访问Python每天3分钟系列。
每天花3分钟时间,学习或温习一个Python知识点。
今天是第039篇:
参数传递和内存结构 – 修正版
昨天讲的话题,有的麦友表示代码没有输出结果,看不懂。也有表示还不够清楚,所以今天重新发一次,添加了代码结果,也大幅完善了说明。
“本文是我的电子书麦叔带你学Python中相关的章节复制和修改。
“完整电子书可以在麦叔编程公众号上回复book1获取。
当调用函数的时候,需要传递参数,变量从一个函数传递到了另一个函数。
这个传递过程发生了?被传递的变量是被复制了一份呢?还是两个函数共用同一个对象呢?
分清楚变量和对象
首先我们要分清楚变量和对象的区别和关系。在这行代码name="麦叔"
中实际上创建了两个东西:
-
一个是name变量,它在内存中有一块小的空间,里面保存了变量所代表的对象的内存地址。这个地址可以改变。 -
一个是字符串”麦叔”,它在内存中有自己的存放空间,里面存放具体的数据。
如下图所示:
做一个类比:有一个冰箱,上面有一个便签贴,上面写着麦叔001。这个便签条就是变量,而冰箱就是对象。
在编程的世界中,便签条上除了有个标识符麦叔001,还有一个地址记录了麦叔001所代表的冰箱存放的位置。
这个图中贴了好几个便签条。在编程中也可以这么干,可以有好多变量实际上指向了同一个对象。这种情况下,如果通过任何一个变量修改了对象的内容,那就都修改了,因为毕竟都是同一个对象。
可变数据类型和不可变数据类型
我们再来理解一下变量和对象的内存结构,尤其是list等集合类型的内存结构:
name = '麦叔'
print(id(name))
name = '张三'
print(id(name))
函数id()返回一个对象的内存地址,上面的代码打印结果如下:
4348256848
4348256944
上面打印的内存地址是不同的,这表明麦叔和张三是两个不同的对象。
在内存里,它的过程是这样的:
-
变量name有一个自己的空间(蓝色方块),存放它所代表的对象的地址(黄色方块)。 -
一开始,变量名 name 指向了“麦叔”对象;后来变量名name指向了“张三”。 -
这时候“麦叔”不再有变量指向它,因而成为孤魂对象,这种对象会被Python的垃圾回收器销毁掉,因为没用了嘛。
要点总结:
对于字符串,数字等数据类型,每次我们修改一个变量的值的时候,Python会创建一个新的对象,把变量指向这个新的对象。
所以字符串等被称为不可变对象,因为对象一旦创建就不能修改。修改实际上是创建了新的对象。
来看看复杂的数据类型,比如 list:
cities = ['北京', '上海', '广州', '深圳']
print(id(cities))
cities.append('雄安')
print(id(cities))
打印结果:
4307939968
4307939968
上面创建了一个列表cities,然后给cities中间添加了城市雄安。两次打印内存地址是完全相同的,说明是同一个对象。
这说明了:list, set, dict等对象的内容是可以被修改的,并不会因为修改而从新创建对象。所以它们被称为可变对象。
如下图所示
-
cities是一个普通的变量,和前面的name没有什么区别,它里面保存了list对象的地址。 -
list对象就是下图中间的部分,它里面存放了很多东西,所谓存放只不过是保存了具体对象的地址。就好像有班级花名册,记录同学名字和住址。 -
当list中添加或者减少一个对象时,只是更新花名册上的名字就可以了,不需要每次都新建一个花名册。
下面列举了Python中的可变对象(Mutable Types)和不可变对象(Immutable Type):
参数传递和引用次数
理解了变量的内存存储结构,我们再来看本文开始的的问题,参数传递到底是传递了什么?是复制了一份吗?
先说结论:参数和返回值的传递都是传递的变量指向的内存地址:
import random
name = '麦叔'
print(f'name的地址:{id(name)}')
def shuai_score(person):
print(f'name的地址:{id(person)}')
score = random.randint(1, 10)
print(f'score的地址:{id(score)}')
return score
mscore = shuai_score(name)
print(f'mscore的地址:{id(mscore)}')
打印结果:
name的地址:4345848400
name的地址:4345848400
score的地址:4344381712
mscore的地址:4344381712
代码说明:
-
name被传给了shuai_score函数,传递的是内存地址,所以两次打印地址是相同的。 -
返回的score也是内存地址,所以两次打印地址也是相同的。
再来看下一个例子:
name = 'zhangsan'
print(f'外部name的地址:{id(name)}')
def hello(name):
print('----------')
print(f'传进来name的地址:{id(name)}')
name = 'lisi'
print(f'修改过的name的地址:{id(name)}')
print('----------')
hello(name)
print(f'外部name的新地址:{id(name)}')
打印结果:
外部name的地址:4339450800
----------
传进来name的地址:4339450800
修改过的name的地址:4339450928
----------
外部name的新地址:4339450800
代码说明:
-
最开始name是zhangsan,地址是4339450800。这个name是全局变量。 -
调用hello(name)的时候,全局变量name被传递给了函数hello(),这时候hello创建了一个局部变量,碰巧也叫name。这两个虽然都叫name,但是不同的变量。但它们同时指向了对象zhangsan,所以在函数打印时的时候,内存地址也是4339450800。 -
在函数内局部变量的值改成了lisi,这时候Python创建了一个新的对象,并把局部变量name指向了这个新的对象。所以再次打印地址成了4339450928。 -
这个过程并不会影响外部的全局变量,所以外部再次打印,还是4339450800。
简单来说:
-
全局变量 name 指向zhangsan,这一直都没有变过; -
局部变量 name 本来是指向zhangsan的,但是后来指向了lisi,这并不影响全局变量还是指向张三。
再来看一个可变对象的例子:
cities = ['北京', '上海', '广州', '深圳']
print(f'全局cities的地址:{id(cities)}')
def add_city(cities):
print(f'局部cities的地址:{id(cities)}')
cities.append('雄安')
print(f'局部cities的地址:{id(cities)}')
add_city(cities)
print(cities)
print(f'全局cities的地址:{id(cities)}')
打印结果:
全局cities的地址:4378653312
局部cities的地址:4378653312
局部cities的地址:4378653312
['北京', '上海', '广州', '深圳', '雄安']
全局cities的地址:4378653312
代码说明:
-
cities变量被传递给add_city函数,这时候局部变量和全局变量同时指向同一个list对象。 -
在add_city中增加了雄安,但因为list是可变对象,所以这个修改不会改变list对象地址,所以全局变量和局部变量仍然指向同一个对象。 -
所以多次打印cities的地址,都是同一个。函数中的改变,也影响了外部的变量的值,因为本来就是同一个对象嘛。
评论(0)