Python学习笔记——函数参数,按值传递还是按引用传递?(变量是对象引用、变量只存储值的内存地址)

函数参数,按值传递 还是 按引用传递?

单纯从行为上看,Python的函数参数语义“支持按值调用支持按引用调用”,不同情况下,其表现不同

更准确的说,Python函数的调用语义是:按对象引用调用语义

Python中的变量并不是与其他编程语言中的变量不同,这些变量是对象引用
可以变量中存储的值认为是值的内存地址,而不是它真正的值
传递参数时,会把这个内存地址传入函数(而非传入实际的值)

传递参数时,发生了什么

实际上,传递参数时解释器会查看对象引用(内存地址)指示的那个值的类型

  • 如果其为可变的,就会应用按引用传递;(如列表、字典、集合)
    在函数中,修改可变类型的参数,其本身会改变

  • 如果是不可变的,则会应用按值传递(如字符串、整数、元组)
    在函数中,修改不可变类型的参数,其本身不改变

  • 另外要注意,传入的参数是否真正改变,不是看函数参数的变量内容是否改变,而应该看传入的原始对象的对象引用(内存地址)指向的值是否改变
    (详见下面的例子)
    利用这个细节,可以用赋值语句让可变型参数表现得像按值传递
    (见文末的例子)

对于不可变类型的参数,按值传递

这意味着在函数中,修改不可变类型的参数,其本身不改变

def double(arg):
    print('Before: ', arg)
    arg = arg * 2
    print('After:  ', arg)

================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> num=2
>>> double(num)
Before:  2
After:   4
>>> num
2

>>> saying='Hello'
>>> double(saying)
Before:  Hello
After:   HelloHello
>>> saying
'Hello'

>>> nums=[1,2,3]
>>> double(nums)
Before:  [1, 2, 3]
After:   [1, 2, 3, 1, 2, 3]
>>> nums
[1, 2, 3]
最后一个例子似乎不太对?赋值语句“阻止了”按引用传递

最后一个例子中,传入一个列表(可变类型),然后调用函数后,列表并未改变
这是bug吗?这个列表怎么按值传递了?

其实并不是。
是否修改了传入的参数:不是看最开始用于保存参数的arg变量是否改变(Python中变量存储的都是对象引用而不是实际的值),而是应该看(传入参数时)原始参数的对象引用(内存地址)指向的值是否改变

在这里,函数中并未改变传入的列表nums
关键问题在于赋值语句

arg = arg * 2
  • 这句话是赋值语句
  • =右边:对原来的列表*2操作,得到另一个新的列表对象
  • =左边:将新对象的对象引用赋给等号左边的变量arg
  • 赋值语句虽然覆盖变量arg原先存储的对象引用,但原始的“老”对象引用仍存在,且其指向的值未改变,所以解释器仍看到原来的列表

如果改为

arg[:] = arg * 2

即可实现原地修改,原来的列表也会被改变


下面的例子中,使用列表的append(),则成功改变了“传入的原始列表”的对象引用所指向的值,从而体现“可变参数按引用传递”(传入的参数改变,其本身也改变)

对于可变类型的参数,按引用传递

这意味着在函数中,修改可变类型的参数,其本身也会改变

def change(arg):
    print('Before: ', arg)
    arg.append('More data')#修改了原始对象引用指向的值
    print('After:  ', arg)

================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> nums=[1,2,3]
>>> change(nums)
Before:  [1, 2, 3]
After:   [1, 2, 3, 'More data']
>>> nums
[1, 2, 3, 'More data']

利用赋值语句,防止按引用传递

根据上面的讨论,我们知道

  • 如果让函数存储参数的变量(通过赋值语句不再指向原始对象,就能防止改变传入函数的可变参数
  • 从而不再“按引用传递”,而是表现得像“按值传递”

例如,向上一个程序中增加arg = arg.copy()赋值语句

def change(arg):
    print('Before: ', arg)
    
    arg=arg.copy()#注意这个赋值语句,让arg指向了一个新对象(而不是原始对象)
    
    arg.append('More data')
    print('After:  ', arg)

================= RESTART: C:\Users\13272\Desktop\mystery.py =================
>>> nums=[1,2,3]
>>> change(nums)
Before:  [1, 2, 3]
After:   [1, 2, 3, 'More data']
>>> nums
[1, 2, 3]
#就像“按值传递”一样,不改变其本身
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值