Python 列表传参函数内修改影响原列表

FreeGuideOnline 13阅读 2026-07-04

python def add_item(lst): lst.append('新元素')

my_list = [1, 2, 3] add_item(my_list) print(my_list) # 输出: [1, 2, 3, '新元素']


调用函数后,`my_list` 被添加了一个元素。这和我们直觉中“函数应该操作副本”不同。要理解原因,必须先弄清 Python 的变量与对象模型。

## 原理剖析:可变对象与引用传递

### 一切皆对象,变量只是标签

在 Python 中,变量不是存储数据的盒子,而是指向内存中对象的 **引用(标签)**。当你写下:

```python
a = [10, 20]

实际上是创建了一个列表对象,并把标签 a 贴在这个对象上。

函数参数传递的是引用的副本

Python 的参数传递机制是“传对象引用”,也称“传引用的值”。当调用 add_item(my_list) 时:

  1. 实参 my_list 持有的引用被复制一份;
  2. 该副本赋值给形参 lst
  3. 现在 my_listlst 都指向 同一个列表对象

因此,通过 lst 对该对象进行原地修改(如 appendextendpop、元素赋值等),会直接反映到外部的 my_list 上。

可变对象 vs 不可变对象

  • 可变对象:列表、字典、集合、自定义类实例等,内容可被原地更改。
  • 不可变对象:整数、字符串、元组、布尔值等,一旦创建就不能修改其内部状态。

如果参数是不可变对象,函数内对它的“修改”实际上是创建了新对象并重新绑定形参,不会影响外部变量。

def change_str(s):
    s = s + ' world'   # 创建新字符串,s 指向新对象

greeting = 'hello'
change_str(greeting)
print(greeting)   # 输出: hello (未变)

常见“踩坑”场景与规避方法

一、意外修改原列表

当你只是想在函数内使用列表数据而不改变原列表时,需要创建副本。

错误做法

def process_data(data):
    # 本意只是临时排序
    data.sort()
    return sum(data[:3])

scores = [5, 2, 9, 1, 7]
result = process_data(scores)
print(scores)   # [1, 2, 5, 7, 9]  原列表被永久排序了!

正确做法:使用切片或 copy 模块

def process_data_safe(data):
    data_copy = data[:]          # 浅拷贝切片,或用 list(data)、data.copy()
    data_copy.sort()
    return sum(data_copy[:3])

scores = [5, 2, 9, 1, 7]
result = process_data_safe(scores)
print(scores)   # [5, 2, 9, 1, 7]  原列表不变

二、嵌套列表与深拷贝

列表中的元素若本身也是可变对象(如列表嵌套),浅拷贝仅复制外层容器,内层对象仍然共享。

def nested_modify(data):
    data[0][0] = 99

matrix = [[1, 2], [3, 4]]
nested_modify(matrix[:])   # 传递浅拷贝,以为安全
print(matrix)   # [[99, 2], [3, 4]]  原列表被修改!

要完全隔离,需要使用 copy.deepcopy

import copy

def nested_modify_safe(data):
    data = copy.deepcopy(data)
    data[0][0] = 99

matrix = [[1, 2], [3, 4]]
nested_modify_safe(matrix)
print(matrix)   # [[1, 2], [3, 4]]  原列表不变

三、默认参数中的可变陷阱

在定义函数时,将可变对象(如空列表)作为默认参数值,会导致多次调用共享同一对象。

# 错误示例
def add_to_list(item, target=[]):
    target.append(item)
    return target

print(add_to_list(1))   # [1]
print(add_to_list(2))   # [1, 2]  预期是 [2]

解决方案:默认参数设为 None,在函数内部创建新列表。

def add_to_list(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

合理利用“原地修改”的优势

理解这一特性后,你可以写出更高效、更地道的 Python 代码。

1. 避免不必要的数据复制

当处理大型列表时,返回新对象的开销较大。选择原地操作可以节省内存和时间。

def normalize(data):
    max_val = max(data)
    for i in range(len(data)):
        data[i] /= max_val
    # 无需返回,调用方自然看到变化

2. 实现“输出参数”模式

通过传递空的列表或字典,让函数填充结果,常用于返回多个值或复杂结构。

def collect_errors(log, error_list):
    for entry in log:
        if 'error' in entry:
            error_list.append(entry)

errors = []
collect_errors(server_log, errors)
print(f"发现 {len(errors)} 个错误")

3. 递归处理嵌套结构

原地修改可以优雅地更新嵌套数据。

def double_all(tree):
    for i, item in enumerate(tree):
        if isinstance(item, list):
            double_all(item)       # 递归修改子列表
        else:
            tree[i] = item * 2

nested_numbers = [1, [2, 3], 4]
double_all(nested_numbers)
print(nested_numbers)   # [2, [4, 6], 8]