编程Python开发者 / 数据工程师

在Python中,将可变对象(例如列表或字典)传递给函数时会发生什么?如何避免在函数内部和外部的意外更改?

用 Hintsage AI 助手通过面试

答案。

问题背景:

在Python中,参数传递实现了"按对象引用调用"的原则(有时称为按共享调用)。这意味着函数内部的变量开始指向与外部传递的参数相同的内存对象。

问题:

如果函数修改了传入的可变对象(例如列表或字典),变化在函数外部也是可见的。这可能导致难以捕捉的错误,特别是当期望函数不会修改输入数据时。

解决方案:

为了避免副作用,应在函数内部创建对象的副本或使用不可变数据结构。复制时可以使用标准方法(例如,列表的list.copy()、字典的dict.copy()或copy.deepcopy())。

代码示例:

def append_one(xs): xs.append(1) return xs lst = [0] append_one(lst) print(lst) # [0, 1] # 如何避免更改?创建副本: def safe_append_one(xs): ys = xs.copy() ys.append(1) return ys lst2 = [0] safe_append_one(lst2) print(lst2) # [0]

关键特性:

  • 传递可变对象允许在函数内外更改其状态。
  • 为了避免这种情况,使用数据复制(浅拷贝/深拷贝)。
  • 不可变对象受到此类更改的保护。

误导性问题。

通过.copy()复制的列表副本可以完全独立于原始列表吗?

不可以 — .copy() 创建的是浅拷贝。如果内部有嵌套的可变对象,变更会在原件中显示。

import copy lst = [[1, 2], [3, 4]] shallow = lst.copy() shallow[0][0] = 42 print(lst) # [[42, 2], [3, 4]] deep = copy.deepcopy(lst) deep[0][0] = 100 print(lst) # [[42, 2], [3, 4]]

基于输入返回新对象是否保证原件不被更改?

不一定。如果新对象内部使用原始对象的部分(例如,指向内部嵌套列表的引用),原始对象可能会被更改。

def duplicate_list(xs): return xs * 2 lst = [[1], [2]] res = duplicate_list(lst) res[0][0] = 999 print(lst) # [[999], [2]]

默认可变对象的参数在多次调用函数时会导致问题吗?

是的 — 默认值只在函数定义时计算一次。

def add_item(item, container=[]): container.append(item) return container print(add_item(1)) # [1] print(add_item(2)) # [1, 2]

常见错误和反模式

  • 在函数内部更改传递的可变对象而不通知用户。
  • 对于嵌套数据结构使用浅拷贝(处理可变嵌套对象的错误)。
  • 在函数参数中使用可变对象作为默认值。

生活中的例子

负面案例

在配置处理库中使用列表作为默认值,这导致在不同函数调用之间累积元素。行为不可预测,且检测时间较长。

优点:

较少的代码用于重复调用,明显的内存节省。

缺点:

隐式行为,调试困难,长期错误。

正面案例

使用None作为默认值,并在每次调用时显式创建新对象。

优点:

可预测性,没有意外的副作用,可靠性。

缺点:

需要意识到并多写一点代码。