Python 函数里的空列表,为什么会记住上一次调用

举报
TechCao 发表于 2026/11/06 06:13:40 2026/11/06
【摘要】 用三个可运行示例说明 Python 默认列表的共享时机、None 默认值与显式传入空列表的区别,以及浅复制为何只能隔离外层容器。

写一个收集任务名称的小函数时,我们可能把 items=[] 当作“每次没有传入列表,就创建一个空列表”。第一次运行结果正常,第二次却带出了上一次的数据。若类似函数运行在长期存活的服务中,这种隐式共享还可能让不同请求的内容混在一起。

1. 问题出在对象的创建时机

下面的代码只依赖 Python 标准语法,可以直接运行:

def collect(name, items=[]):
    items.append(name)
    return items


first = collect('build')
print(first)                    # ['build']
second = collect('test')
print(second)                   # ['build', 'test']
print(first is second)           # True
print(first)                    # ['build', 'test']

函数的默认参数表达式在执行函数定义时求值,不是在每次调用时重新求值。这个列表因此被保存在函数的默认值中;之后省略 items 的调用都指向同一个对象。append() 又会原地修改列表,于是数据跨调用累积。

还要留意最后一行:变量 first 并不是第一轮结果的快照,它仍然引用那个列表。因此第二次调用后,重新读取 first 也会看到两个名称。只检查返回内容,容易漏掉这种对象别名关系。

2. 用 None 表达“调用者没有提供容器”

如果约定 None 表示未提供列表,可以在函数体里创建默认对象:

def collect(name, items=None):
    if items is None:
        items = []
    items.append(name)
    return items


first = collect('build')
second = collect('test')
print(first)                    # ['build']
print(second)                   # ['test']
print(first is second)           # False

provided = []
result = collect('release', provided)
print(result is provided)        # True
print(provided)                 # ['release']

此时,省略参数的两次调用分别得到两个列表。显式传入列表时,函数仍然修改调用者提供的对象。这是一个需要写进接口说明的行为:修复默认参数共享,并不等于所有输入都会被复制。

判断条件应使用 items is None,而不是 if not items。空列表虽然是假值,但它也是调用者明确传入的有效容器。如果把它替换成新列表,就会改变“向指定容器追加”的语义。同样,items = items or [] 也有这个问题。

3. 如果不允许修改输入,就明确复制

有些接口希望返回新结果,并保留输入列表原样。这时可以采用另一种约定:

def collected(name, items=None):
    result = [] if items is None else list(items)
    result.append(name)
    return result


source = ['build']
result = collected('test', source)
print(source)                   # ['build']
print(result)                   # ['build', 'test']
print(result is source)          # False

list(items) 是浅复制:外层列表变了,内部若包含字典或其他列表,嵌套对象仍然共享。它适合这里存放名称字符串的场景;若后续会修改嵌套对象,需要另外定义复制边界,不能仅凭外层对象不同就认为完全隔离。

这个问题也适用于默认字典和集合等可变对象。默认参数使用不可变对象时,通常不会产生同样的原地累积;但把可变列表包在元组里,也不会让内部列表变得不可变。若 None 本身是合法业务值,应使用独立哨兵对象区分“没有传参”和“明确传入 None”。

验证这类函数时,至少覆盖连续两次省略参数、显式传入空容器、检查输入是否被修改这三种情况。把对象归属说明白,才能判断某次修改究竟是接口承诺,还是意外的数据串用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。