有如下函数在服务中被多次调用:def append_item(item, bucket=[]): bucket.append(item); return bucket。线上发现不同请求之间出现数据“串扰”。以下哪项判断与修复建议最合理?
问题根源:Python 默认参数在函数定义时只创建一次
def append_item(item, bucket=[]): bucket.append(item) return bucket
其中 bucket=[] 这个默认列表 在整个程序生命周期内只创建一次,并被所有未显式传入 bucket 的调用共享。
在 Web 服务中的后果(比如 Flask / FastAPI / Django)
假设你这样用它处理请求:
@app.route("/add/<item>") def add_item(item): result = append_item(item) # 没传 bucket,用默认 [] return str(result)
那么:
/add/apple → 返回 ['apple']/add/banana → 返回 ['apple', 'banana'] ❗/add/cherry → 返回 ['apple', 'banana', 'cherry'] ❗👉 不同用户的请求之间数据互相污染了!这就是“串扰”。
因为所有请求都共用同一个 bucket 列表对象(即函数默认参数的那个 [])。
即使你在不同时间、不同上下文调用,只要没传 bucket,就一直在往同一个列表里塞数据。
✅ 正确写法:用 None 作默认值
核心原因:None 是不可变的单例对象,且语义上表示“无值”
None 作为默认参数,在函数定义时被绑定一次 —— 但这没关系!None 是不可变的,你不会去修改它(比如 None.append(...) 会报错)。None,就新建一个空列表。[],彼此隔离。关键不是 None 本身多神奇,而是:你用它作为一个“哨兵值”(sentinel value),触发创建新对象的逻辑
def append_item(item, bucket=None): if bucket is None: bucket = [] # 每次调用都新建一个列表 bucket.append(item) return bucket
现在:
print(append_item("A")) # ['A'] print(append_item("B")) # ['B'] print(append_item("C")) # ['C']
每个调用都是独立的,不会串扰。
补充知识:如何查看这个“共享”对象?
你可以打印 id(bucket) 看地址:
def append_item(item, bucket=[]): print("bucket id:", id(bucket)) bucket.append(item) return bucket append_item(1) # bucket id: 140234... append_item(2) # bucket id: 140234... ← 相同!
两次调用的 bucket 是同一个对象!
这是 Python 经典陷阱之一,也是很多线上 bug 的来源
那能不能用其他值代替 None?
理论上可以,但要满足两个条件:
用 ()(空元组)或其他不可变对象?
def append_item(item, bucket=()): if bucket == (): bucket = [] bucket.append(item) return bucket
不推荐! 因为:
(),你就误以为是“没传参数”;== 比较不如 is 快(虽然差别极小);() 和列表毫无关系,让人困惑。
❌ 绝对不能用的替代:
bucket=[] → 会共享(就是你的 bug 来源)bucket={} → 同样会共享(字典也是可变对象)bucket=set() → 同样会共享bucket=""(空字符串)→ 虽然不可变,但用户可能真的传空字符串,导致逻辑错误用 None 不是因为它特殊,而是因为它是最简单、最安全、最符合习惯的“哨兵值”
| 约定俗成 | Python 社区广泛接受 None 表示“未提供” |
“哨兵”之所以叫哨兵,是因为它站在代码的边界上,默默监视着“有没有人来(传参)”,一旦发现没人,就触发默认行为。