学习python的第十三天之函数——函数传参中的传值和传址问题
学习python的第十三天之函数——函数传参中的传值和传址问题
函数传参中的传值和传址问题
函数传参的机制可以理解为传值(pass-by-value)和传址(pass-by-reference)的混合体,但实际上更接近于传对象引用(pass-by-object-reference)。
不可变类型(传值?)
对于不可变类型(如整数、浮点数、字符串、元组等),当你将它们作为参数传递给函数时,似乎是在传值,因为任何在函数内部对这些参数所做的修改都不会影响到函数外部的变量。
def modify_value(x):
x = 10 # 解释:相当于将10的地址给到临时变量x,替换了原来5的地址
print("Inside function:", x, id(x)) # 解释:所以打印的地址是10的地址
a = 5 # 解释:将5的地址给到a
modify_value(a) # 解释:相当于将a的地址给到modify_value函数中的临时变量x
print("Outside function:", a, id(a)) # 解释:a的地址是5的地址
# Inside function: 10 2450059455056
# Outside function: 5 2450059454896
# 地址发生变化,因为小整数池的原因
在这个例子中,a
是一个整数对象,当它被传递给 modify_value
函数时,函数内部得到的是 a
的一个副本(在内存中是一个新的整数对象 10),而不是 a
本身。因此,函数内部对 x
的修改不会影响到外部的 a
。
然而,这并不是说 Python 在传递这些值时进行了完整的拷贝。实际上,Python 传递的是对不可变对象的引用,但由于这些对象是不可变的,所以你不能通过引用改变它们的内容。当你尝试改变它们时,你实际上是在创建一个新的对象。
可变类型(传址?)
对于可变类型(如列表、字典、集合等),当你将它们作为参数传递给函数时,函数内部可以直接修改这些对象的内容,这种行为看起来像是传址。
def modify_list(lst):
lst.append(10) # 解释:因为list列表是可变类型,内容发生变化后地址不会变,所以lst的地址还是传进来的my_list的地址2077876147584
print("Inside function:", lst, id(lst)) # 解释:所以打印的地址还是my_list的地址2077876147584,但是内容变换了
my_list = [1, 2, 3]
modify_list(my_list) # 解释:相当于将my_list的地址2077876147584给到modify_list函数中的临时变量lst
print("Outside function:", my_list, id(my_list)) # 解释:因为在函数中将地址2077876147584的列表内容进行修改了,但是my_list的地址没变,还是指向2077876147584,所以my_list的内容也跟着变化了
# Inside function: [1, 2, 3, 10] 2077876147584
# Outside function: [1, 2, 3, 10] 2077876147584
# 地址没发生变化,因为list列表为可变类型
在这个例子中,my_list
是一个列表对象,当它被传递给 modify_list
函数时,函数内部得到的是对同一个列表对象的引用。因此,函数内部对 lst
的修改会影响到外部的 my_list
。
传址和传值的对比解释(重要)
前情提要:如过函数中给入的参数是函数中有的变量,则在这个函数中变量会作为局部变量使用,不会影响到外面同名的变量。
在Python中,当我们提到“对象的地址”或“对象的内存地址”时,我们实际上是在谈论一个更抽象的概念,即对象的标识符。这个标识符是由Python解释器在内部生成的,用于唯一地标识每个对象。虽然标识符在底层实现上可能与对象的实际内存地址有关,但Python解释器并不直接暴露内存地址给开发者。标识符是一个更高级的抽象,它允许Python在内存管理方面保持更大的灵活性。
list1 = ['a', 'b', 'c', [1,3,5]]
list2 = list1[::-1]
list3 = list1[1:-1]
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2464707002304
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2464707002304
print(list3,id(list1)) # 输出: ['b', 'c'] 2464707002304
print('-----------------------------------')
def func1(l1, l2):
l1 = l2
l2.append(10)
func1(list2, list3)
print('-----------------------------------')
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2464707002304
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2464707002304
print(list3,id(list1)) # 输出: ['b', 'c', 10] 2464707002304
# func1(list2, list3)实际上是将list2的‘身份标识符’给到了函数里的l1,将list3的‘身份标识符’给到了函数里的l2,
# 因为函数中存在l1这个赋值变量,但是为什么l2不算是临时变量呢?
# 是因为l1 = l2这句,通俗的说你要存放一个东西,无论这个东西是什么,你要先有一个位置,
# l1就是这个位置,l2就是这个东西,而l2这个东西的来源就是靠调用func1()这个函数来分配的;
# 同样l2.append(10)这一句,只是对这个未知的l2进行的一步操作,也没有创建一个叫l2的临时变量,
# 综上,函数中的l1是临时变量,所以对l1的赋值操作不会影响到给予‘身份标识符’的list2,
# 但是l2不是临时变量,所以l2指代的就是list3,所以l2的操作就会影响到给予‘身份标识符’的list3。
# 如果函数改为:
list1 = ['a', 'b', 'c', [1,3,5]]
list2 = list1[::-1]
list3 = list1[1:-1]
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2587051900864
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2587051900864
print(list3,id(list1)) # 输出: ['b', 'c'] 2587051900864
print('-----------------------------------')
def func2(l1, l2):
l1 = l2
l2 = l2[::-1]
func2(list2, list3)
print('-----------------------------------')
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2587051900864
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2587051900864
print(list3,id(list1)) # 输出: ['b', 'c'] 2587051900864
# 可以发现,这次l2就没有影响到list3,因为这次l2也变成了临时变量
# 同理就算l1换成list1,l2换成list2,函数中的list1和list2也只是存在与函数中,和全局的list1、list2不是同一个;
list1 = ['a', 'b', 'c', [1,3,5]]
list2 = list1[::-1]
list3 = list1[1:-1]
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2221755744192
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2221755744192
print(list3,id(list1)) # 输出: ['b', 'c'] 2221755744192
print('-----------------------------------')
def func2(list1, list2):
list1 = list2
list2.pop()
func2(list2, list3)
print('-----------------------------------')
print(list1,id(list1)) # 输出: ['a', 'b', 'c', [1, 3, 5]] 2221755744192
print(list2,id(list1)) # 输出: [[1, 3, 5], 'c', 'b', 'a'] 2221755744192
print(list3,id(list1)) # 输出: ['b'] 2221755744192
# 结果就是函数中的list1临时变量既没有影响到全局同名的list1,也没有影响到给予‘身份标识符’的list2;
# 函数中的list2(非临时变量),只影响到给予‘身份标识符’的list3,而没有影响到全局同名的list2;
总结
更准确地说,Python 在函数传参时传递的是对对象的引用。对于不可变对象,由于你不能改变它们的内容,所以这种传递方式看起来像是传值。对于可变对象,由于你可以改变它们的内容,所以这种传递方式看起来像是传址。