Python函数中NumPy数组与列表是否会被修改?如何避免原值改动?
传入Python函数的列表与NumPy数组是否会被内部操作修改
Python的参数传递是对象引用传递,不存在其他语言里非黑即白的值传递/引用传递划分,传入的列表和NumPy数组会不会被修改,完全取决于函数内部对传入对象执行的操作,两类对象的具体表现如下:
- 列表的修改规则
列表是典型的可变对象,如果函数内部对传入的列表执行原地修改操作,外部的原始列表会被直接改动,常见的原地操作包括:调用append()/pop()/extend()/sort()这类原地方法、按索引/切片给元素赋值、用+=做列表拼接。
示例代码:
如果函数内部只是把传入的参数变量重新指向新的列表对象,或者执行def change_list(input_list): input_list.append(4) input_list[0] = 999 origin = [1,2,3] change_list(origin) print(origin) # 输出 [999, 2, 3, 4],原始列表已被修改sorted()、列表+运算这类返回新对象的非原地操作,就不会影响外部的原始列表。 - NumPy数组的修改规则
NumPy数组同样是可变对象,且其设计偏向高性能,大量操作默认是原地执行,只要函数内对传入的数组做原地修改(按索引/切片赋值、调用sort()/fill()这类原地方法、用+=/*=这类原地运算符),都会直接改动原始数组。
要特别注意:NumPy数组的切片默认是原数组的视图,不是独立拷贝,哪怕函数里只修改了原数组的切片,也会联动修改原始数组——这一点和Python原生列表不同,原生列表的切片是浅拷贝,修改切片不会影响原列表。
示例代码:
和列表一样,如果函数内部只是把参数变量重新赋值为新的数组对象,不会对外部原始数组产生任何影响。import numpy as np def change_arr(arr): arr[0] = 100 arr *= 2 origin_arr = np.array([1,2,3]) change_arr(origin_arr) print(origin_arr) # 输出 [200 4 6],原始数组已被修改
避免原始值被意外修改的可行措施
- 传入前主动生成独立副本
这是最直接、最不容易出问题的方案:- 对普通列表:如果元素都是数字、字符串这类不可变类型,用
origin.copy()、list(origin)或者切片origin[:]生成浅拷贝即可;如果列表嵌套了列表、字典这类可变元素,需要用copy.deepcopy(origin)生成递归深拷贝,才能完全切断和原对象的关联。 - 对NumPy数组:直接调用
origin_arr.copy()即可生成内存完全独立的深拷贝,把这个副本传入函数,后续任何操作都不会影响原始数组。
- 对普通列表:如果元素都是数字、字符串这类不可变类型,用
- 函数内部做防御性拷贝
如果你是函数的开发者,且函数逻辑本身不应该改动用户传入的原始数据,可以在函数入口处第一时间对传入参数做拷贝,后续所有逻辑都在副本上执行。比如处理数值数据的函数可以在开头写arr = np.array(arr, copy=True),不管用户传入的是列表还是NumPy数组,都会自动生成独立副本,从根源上避免误改外部数据。 - 刻意规避容易触发原地修改的写法
- 操作列表时,优先用返回新对象的非原地方法:比如排序用
sorted(input_list)代替input_list.sort(),拼接元素用new_list = input_list + [elem]代替append()。 - 操作NumPy数组时,对传入的原始数组避免使用
+=/-=这类原地运算符,换成arr = arr * 2这类生成新数组的写法;如果需要操作数组切片,先给切片做.copy()再修改,避免视图联动改到原数组。
- 操作列表时,优先用返回新对象的非原地方法:比如排序用
- 调试阶段用只读锁做校验
排查误改问题时,可以在传入NumPy数组前把它设为只读:origin_arr.flags.writeable = False,之后任何尝试修改该数组的操作都会直接抛出ValueError,可以快速定位到误改的代码位置。
注意:浅拷贝只会复制最外层的容器对象,容器内部存储的可变元素依然和原对象共享引用,只要存在嵌套可变结构的场景,要完全隔离原始值必须使用深拷贝。
内容的提问来源于stack exchange,提问作者Prathvik G S
相关产品推荐
相关产品推荐

