参数传递机制
Python 的参数传递方式常被误解为"传值"或"传引用"。实际上,Python 使用的是传对象引用(call by object reference 或 call by assignment):函数接收的是对象的引用(类似指针),但引用本身是按值传递的。
核心规则
def modify_list(lst):
lst.append(4) # 修改对象本身
def reassign_list(lst):
lst = [1, 2, 3] # 让 lst 指向新对象
a = [1, 2, 3]
modify_list(a)
print a # [1, 2, 3, 4] —— 被修改了!
b = [1, 2, 3]
reassign_list(b)
print b # [1, 2, 3] —— 没变!
关键区别:
lst.append(4):通过引用找到原对象,修改对象的内容lst = [1, 2, 3]:创建新对象,让局部变量lst指向它,不影响外部变量b
不可变对象的传递
数字、字符串、元组是不可变对象,函数内任何"修改"都是创建新对象:
def increment(x):
x += 1
print "inside:", x # 11
n = 10
increment(n)
print "outside:", n # 10 —— 没变!
x += 1 创建了新整数对象 11,让局部变量 x 指向它。外部变量 n 仍然指向 10。
def append_suffix(s):
s += " world"
print "inside:", s # "hello world"
text = "hello"
append_suffix(text)
print "outside:", text # "hello" —— 没变!
字符串不可变,s += " world" 创建了新字符串 "hello world"。
可变对象的传递
列表、字典是可变对象,函数内可以修改对象内容:
def add_item(items, item):
items.append(item)
cart = ["apple"]
add_item(cart, "banana")
print cart # ['apple', 'banana'] —— 被修改了!
但如果重新赋值,外部不受影响:
def clear_cart(cart):
cart = [] # 局部变量指向新列表
cart = ["apple"]
clear_cart(cart)
print cart # ['apple'] —— 没变!
常见陷阱
陷阱 1:默认参数的可变对象
def add_item_bad(item, items=[]):
items.append(item)
return items
print add_item_bad(1) # [1]
print add_item_bad(2) # [1, 2] —— 列表被共享了!
默认参数在函数定义时求值,只创建一次。所有调用共享同一个列表。
陷阱 2:修改遍历中的列表
def remove_evens(nums):
for n in nums:
if n % 2 == 0:
nums.remove(n) # 危险!
nums = [1, 2, 4, 3]
remove_evens(nums)
print nums # [1, 4, 3] —— 4 没被删除!
陷阱 3:浅拷贝的误解
def process(data):
copy = data[:] # 浅拷贝
copy[0][0] = 999 # 修改子列表
return copy
matrix = [[1, 2], [3, 4]]
result = process(matrix)
print matrix # [[999, 2], [3, 4]] —— 原矩阵也被改了!
浅拷贝只复制一层,子列表仍然共享。
如何安全地传递参数
需要修改外部对象时:直接修改对象内容
def sort_in_place(nums):
nums.sort() # 原地排序
不需要修改时:创建副本
def get_sorted(nums):
return sorted(nums) # 返回新列表,原列表不变
需要完全独立的副本时:深拷贝
import copy
def process(data):
safe_copy = copy.deepcopy(data)
# 随意修改 safe_copy,不影响外部
return safe_copy
与 C/Java 的对比
| 语言 | 机制 | 效果 |
|---|---|---|
| C | 传值 | 修改参数不影响外部 |
| C(指针) | 传地址 | 可以修改外部变量 |
| Java | 传值(引用) | 可以修改对象内容,不能重新赋值引用 |
| Python | 传对象引用 | 同 Java:可以修改可变对象,不能重新绑定外部变量 |
理解这个机制的关键是区分"修改对象"和"重新赋值变量"。Python 中变量只是名字,名字指向对象。函数内可以修改对象(如果对象可变),但不能让外部名字指向新对象。