返回

Python函数默认字典参数:避开陷阱,正确赋值

python

Python函数默认可变参数的字典重新赋值问题

函数中使用可变对象(例如列表、字典)作为默认参数时,容易产生意外行为。这种行为源于Python的函数参数只在定义时计算一次,之后便持续使用该对象引用。当可变参数被修改时,这种修改会在函数调用间持续保留。这并非预期,经常导致错误。本文聚焦字典这种可变对象,讨论如何正确地重新赋值函数默认参数中的字典。

问题剖析

当函数定义时,字典作为默认参数,它会被创建并绑定到函数对象的属性上。每次调用函数且不传递实参时,都会引用同一个字典实例。如果函数内直接修改此字典,如 d[key] = value 或者 d.update(...),那么字典的内容改变会影响下次调用,结果将不符合预期。以下情况展示了这个问题的本质。

def my_func(data={}):
  data['a'] = 1
  print(data)

my_func() # 输出:{'a': 1}
my_func() # 输出:{'a': 1} 错误,期望值是 {'a': 1} 或者 {}
my_func({'b':2}) # 输出:{'b': 2} 不受之前的操作影响
my_func() # 输出:{'a': 1}  错误,受到之前操作的影响

在第一次调用 my_func() 时,字典初始化为空字典 {} 。但在后续无参调用时,程序使用的是之前修改过的字典 {'a': 1},而不是再次初始化的空字典。这是默认可变参数最容易犯的错误。

解决方案:避免使用可变对象作为默认参数

最简单直接且最推荐的做法,是避免直接使用可变对象作为默认参数,而使用 None 作为占位符,并在函数体内根据需要创建新的字典。

def my_func_correct(data=None):
  if data is None:
      data = {}
  data['a'] = 1
  print(data)

my_func_correct()  # 输出:{'a': 1}
my_func_correct()  # 输出:{'a': 1}  
my_func_correct({'b': 2}) #输出: {'b': 2, 'a': 1}  正确地传递参数时,可以不受默认参数的影响
my_func_correct()  # 输出:{'a': 1}  

这种方法避免了直接操作同一个可变默认参数。在 my_func_correct 中,每次不传实参时都会初始化一个新的字典,使得函数的行为变得可预测。使用 is None 判断而不是 if not data 是出于对 data可能为空字典{}的情况考虑,if not {}会返回True

解决方案:使用字典复制或更新的方式重新赋值

当希望完全用新的内容替换原有的字典,而不保留原有字典的引用时,可以采用字典复制的方法,这也能有效避免问题,并且更清晰易懂。

  1. 使用 copy() 创建新字典: 这种方法会在内存中创建一个新的字典,其内容与原字典相同,后续修改都不会相互影响。
def my_func_copy(data={}):
  if not data:
      data = otherd.copy() # 将 otherd 中的内容复制到新的data
  data['c'] = 3
  print(data)

otherd = {'a':1,'b':2}
my_func_copy()
my_func_copy()
代码中`otherd`是在 `my_func_copy`作用域中已存在的变量。函数中利用`.copy()`方法为data变量创建新的字典对象,后续修改都不会影响到之前创建的变量和默认字典。
  1. 使用 {** ...**}创建新字典: 利用解包运算符,也可以创建新的字典。

    def my_func_unpack(data={}):
      if not data:
         data = {**otherd}
      data['d']=4
      print(data)
    
    otherd = {'a':1, 'b':2}
    my_func_unpack()
    my_func_unpack()
    
    

    这与.copy() 达到了相同的效果。每一次if not data成立时都会新建字典。

  2. 利用 clear 方法和 update 方法: clear() 用于清空原字典的内容,update() 用于使用新字典的键值对填充。

    def my_func_update(data={}):
       if not data:
            data.clear()
            data.update(otherd)
       data['e']=5
       print(data)
    otherd = {'a':1, 'b':2}
    my_func_update()
    my_func_update()
    
    

    这利用现有的字典对象进行更新,看起来避免了默认可变参数的问题,但此操作依旧可能因为默认值是同一个对象而出现问题,建议优先选择copy的方式。

安全建议

处理函数默认参数时,养成如下习惯可以减少错误:

  1. 始终使用 None 作为可变默认参数的占位符。
  2. 在函数内部,根据实际需求判断是否初始化,以及如何初始化。
  3. 如果需要赋值,使用 .copy() 方法或者{**...** } 解包的方式创建一个新的对象进行操作。
  4. 清楚地认识可变对象的引用性质。

通过理解Python的参数传递机制,采用合适的方法,能有效地避免在处理函数默认可变参数时常遇到的问题,编写更加稳定和可靠的代码。