DEFAULT VALUE / 02

默认值记忆实验室

最容易误会的一句话是“没传列表,就给我一个空列表”。真正发生的是:函数定义执行时先做出一个列表,以后没传实参的调用都回到它这里。

只创建
一次

共享默认列表

容易留下上次痕迹
def add_student(name, roster=[]):
    roster.append(name)
    return roster
本次调用帧
name → 小满roster → 默认对象 D0
定义时创建的对象
D0 · 一直被函数保存
    目前还是空列表 []
    调用记录
    1. 尚未调用

    每次新建列表

    每次从干净容器开始
    def add_student(name, roster=None):
        if roster is None:
            roster = []
        roster.append(name)
        return roster
    本次调用帧
    name → 小满roster → 等待调用
    调用时创建的对象
    尚未创建
      每按一次,都会换一个新列表
      调用记录
      1. 尚未调用

      时间点才是这道题的钥匙

      默认值表达式跟着 def 在定义阶段求值,不会等到每次调用再求值。数字和字符串同样只求值一次,只是它们不可变,通常看不出“共用”带来的副作用。

      定义时创建函数对象,同时计算并保存默认值。调用时调用者没给对应实参,参数就绑定到已经保存的那个对象。修改时append 改的是列表本身;下次调用再次见到同一个列表。

      先猜,再验证

      如果显式传入自己的列表 team = [],再执行 add_student("小满", team),会改到哪里?

      落地写法:需要“没传时新建容器”,就用 None 当哨兵,并在函数体内创建;需要有意共享缓存时,才保留可变默认值,而且要把这个行为写清楚。