# Python函数参数是值传递还是引用传递?
面试官问:"Python函数传参是值传递还是引用传递?"
这题最容易答成"Python一切都是引用传递",但这个说法并不准确。真正要讲清楚的是:函数拿到的是同一个对象的引用副本,参数重新绑定和修改可变对象不是一回事。
# 简要回答
Python采用的是对象共享传递(call by object reference / call by sharing),不是传统意义上的值传递或引用传递。
调用函数时,形参会绑定到实参指向的同一个对象。这带来两个行为:
重新绑定形参不会影响外部变量。函数内执行param = new_value,只是让局部名字param指向另一个对象,调用方的变量仍然指向原来的对象。
通过形参修改可变对象,会影响外部看到的同一个对象。函数内执行items.append(x),修改的是调用方和函数共享的那个列表对象,外部自然也能看到变化。
# 详细回答
# 函数调用时发生了什么
Python中,变量是名字,对象独立存在。调用函数时:
- 实参表达式求值,得到对象。
- 形参名字绑定到这个对象。
- 函数内通过形参名访问对象。
- 函数返回后,形参名字消失,但对象是否被修改取决于函数内的操作。
形参与实参绑定同一个对象,不是复制对象内容,也不是传递变量本身的地址。

# 重新绑定不会修改外部对象
def reset_list(items):
items = [] # 重新绑定局部名字items
courses = ["Python", "Go"]
reset_list(courses)
print(courses) # ['Python', 'Go']
2
3
4
5
6
函数内items = []创建了一个新列表,让局部名字items指向它。外部的courses仍然指向原来的列表,所以courses的内容没有变化。
# 修改可变对象会影响外部
def append_item(items):
items.append("C++") # 原地修改对象
courses = ["Python", "Go"]
append_item(courses)
print(courses) # ['Python', 'Go', 'C++']
2
3
4
5
6
items.append("C++")调用列表对象的方法,原地修改了这个列表。因为items和courses共享同一个对象,外部看到的列表自然也被修改了。
# 不可变对象的行为
对于不可变对象(整数、字符串、元组),函数内尝试修改时,Python实际上创建了新对象:
def increment(num):
num = num + 1 # 创建新整数对象并重新绑定
return num
count = 10
result = increment(count)
print(count) # 10,外部变量未变
print(result) # 11
2
3
4
5
6
7
8
num = num + 1计算后生成新整数对象11,num绑定到新对象,count仍然指向10。
# 常见错误:以为传的是变量本身
def swap(a, b):
a, b = b, a # 只是交换局部名字的绑定
x, y = 1, 2
swap(x, y)
print(x, y) # 1 2,外部变量顺序未变
2
3
4
5
6
a, b = b, a只是让函数内的局部名字a和b交换了指向,不会影响外部的x和y。
# 知识拓展
Q1:为什么不说"引用传递"?
传统的引用传递(如C++中的int &x)允许函数直接修改外部变量的绑定。Python函数内重新绑定形参不会影响外部变量,所以不是引用传递。更准确的术语是对象共享传递或传对象引用。
Q2:如何让函数修改外部的不可变对象?
不可变对象本身不能被修改,可以把它包在可变容器里:
def increment_wrapped(container):
container[0] = container[0] + 1
num = [10]
increment_wrapped(num)
print(num[0]) # 11
2
3
4
5
6
或者直接返回新值:
def increment(num):
return num + 1
count = increment(count)
2
3
4
Q3:id()能看出对象是否相同吗?
可以。id(obj)返回对象的身份标识(在CPython中是内存地址)。形参和实参的id()相同,说明它们指向同一个对象。
Q4:函数参数会复制对象内容吗?
不会。无论对象多大,传参只是复制引用(一个指针),不复制对象本身。这也是为什么传递大列表或大字典不会有性能问题。
Q5:如何避免函数意外修改可变参数?
函数内先复制一份:
def process_items(items):
items = items.copy() # 浅拷贝,不影响外部
items.append("new")
return items
2
3
4
或者明确在调用时传副本:
result = process_items(courses.copy())
评论
验证登录状态...