在我们学习和使用Python的过程中往往都会踩一些坑,今天我们要聊的这个问题,不知道你有没有中过招。def add_item(item, items=[]): items.append(item) return itemsprint(add_item("apple")) # ['apple'] 符合预期print(add_item("banana")) # ['apple', 'banana'] 有点疑问print(add_item("cherry")) # ['apple', 'banana', 'cherry'] 为啥会这样
有没有对此感到疑惑呢?在调用add_item("banana")的时候,期望得到的是['banana'],实际上却是['apple','banana']。这就是Python中得经典可变默认参数导致得,也许你也在此上面栽过跟头。在初看到item=[]这个写法时,大概下意识理解成:如果调用函数时,没有传入列表,那就创建一个空列表。但事实证明并不是这样。为什么呢?Python得实际执行逻辑是,在函数定义得那一刻,就创建一个空得列表,之后每次调用函数,只要没传入新得列表,就会重复使用这个已经创建号得列表对象。也就是说,函数得默认值并不是“一个空得列表”,而是指向某个具体列表对象得引用,而这个列表对象会在python第一次读取函数代码时就被创建,之后一直存在,等待被重复调用。让我们再来看一段代码:def add_item(item, items=[]): print(f"列表的唯一标识id: {id(items)}") items.append(item) return itemsadd_item("a") # 列表的唯一标识id: 4399504832add_item("b") # 列表的唯一标识id: 4399504832 ← 同一个对象!add_item("c") # 列表的唯一标识id: 4399504832 ← 还是同一个!
每次调用函数,打印出的列表id都完全相同,这就意味着,我们一直操作的都是同一个列表对象。往里面追加元素时,所有的修改都会被保留,自然会出现 “记忆累加” 的情况。
那要如何来解决这个问题呢?最直接的方式,使用None作为默认值
def add_item(item, items=None): if items is None: items = [] # 每次调用时,创建一个全新的列表 items.append(item) return itemsprint(add_item("apple")) # ['apple']print(add_item("banana")) # ['banana'] 完全符合预期✓
现在,只要调用函数时没有传入items参数,就会触发items = [],每次都创建一个全新的列表对象,彻底解决了 “记忆累加” 的问题。
这个写法并不是只适用于列表,而是所有可变默认参数的通用解决方案,记住这个固定模式,以后遇到列表、字典、集合这类可变对象,直接套用就行:
def process(items=None): if items is None: items = [] # 后续业务逻辑 ...def merge(config=None): if config is None: config = {} # 后续业务逻辑 ...def collect(seen=None): if seen is None: seen = set() # 后续业务逻辑 ...
当然也可能有人觉得,这不过是个小问题,日常开发中注意点就行,没那么重要。但实际情况是,这个小小的问题会引发实实在在的生产环境 bug,而且因为隐蔽性强,排查起来还特别费劲。
最后,不知道你又没有遇到过这个坑呢?如果有希望能在评论区分享一下,看看有多少人中过招。