卡码笔记-最强八股文
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
首页
计算机基础
Python
C++
Java
Go
🔥大模型🔥
  • 大模型面经
  • Java面经
  • C++面经
大模型Github (opens new window)
代码随想录 (opens new window)
  • 本栏必读

    • Python面试题专栏介绍
    • Python学习路线
    • Python零基础入门课
  • 基础语法

  • 函数、作用域与模块

    • Python函数参数是值传递还是引用传递?
    • 位置参数、关键字参数、*args和**kwargs怎么用?
    • Python默认参数为什么不能随便使用列表和字典?
      • 简要回答
      • 详细回答
      • 知识拓展
    • Python中的匿名函数、map、filter和reduce怎么用?
    • Python的LEGB作用域是什么?
    • global和nonlocal有什么区别?
    • 什么是闭包?闭包中的变量为什么会出现延迟绑定?
    • Python模块和包是什么?import是怎么工作的?
  • 面向对象

  • 进阶语法与语言机制

# Python默认参数为什么不能随便使用列表和字典?

面试官问:"下面这段代码有什么问题?"

def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item("Python"))   # ['Python']
print(add_item("Go"))       # ['Python', 'Go']
1
2
3
4
5
6

第二次调用返回['Python', 'Go']而不是['Go'],多数人第一次遇到时都会卡住。问题的根源在于:默认参数在函数定义时创建,所有调用共享同一个对象。

# 简要回答

Python默认参数在函数定义时求值并创建对象,存储在函数对象的属性中。 每次调用时,如果不传递该参数,形参就绑定到这个已经存在的默认对象。

对于可变对象(列表、字典、集合),多次调用会修改同一个对象,导致前一次调用的副作用累积到下一次调用。

正确做法是用None作为默认值,函数内判断并创建新对象:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items
1
2
3
4
5

# 详细回答

# 默认参数何时创建

函数定义时,Python解释器执行def语句,会:

  1. 创建函数对象。
  2. 计算默认参数表达式的值,创建默认对象。
  3. 把默认对象存储在函数对象的__defaults__属性中。
def add_item(item, items=[]):
    items.append(item)
    return items

print(add_item.__defaults__)  # ([],)
1
2
3
4
5

__defaults__是一个元组,包含所有默认参数对象。这个元组在函数定义时创建,之后不会改变。

# 多次调用共享同一个对象

def add_item(item, items=[]):
    items.append(item)
    return items

list1 = add_item("Python")
list2 = add_item("Go")
list3 = add_item("C++")

print(list1)  # ['Python', 'Go', 'C++']
print(list2)  # ['Python', 'Go', 'C++']
print(list3)  # ['Python', 'Go', 'C++']
print(list1 is list2)  # True,三个名字指向同一个列表
1
2
3
4
5
6
7
8
9
10
11
12

每次调用时,如果不传递items参数,形参items就绑定到__defaults__[0]指向的那个列表。三次调用都修改同一个列表,所以list1、list2、list3实际上是同一个对象。

可变默认参数共享对象

# 正确的写法

用None作为哨兵值,函数内判断并创建新对象:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("Python"))   # ['Python']
print(add_item("Go"))       # ['Go']
1
2
3
4
5
6
7
8

每次调用时,如果items是None,就创建一个新列表。不同调用之间不再共享对象。

# 默认字典同理

def add_score(name, score, scores={}):
    scores[name] = score
    return scores

print(add_score("Alice", 90))  # {'Alice': 90}
print(add_score("Bob", 85))    # {'Alice': 90, 'Bob': 85}
1
2
3
4
5
6

两次调用共享同一个字典,第二次调用看到了第一次的副作用。

正确写法:

def add_score(name, score, scores=None):
    if scores is None:
        scores = {}
    scores[name] = score
    return scores
1
2
3
4
5

# 不可变对象作为默认值没有问题

def greet(name, prefix="Hello"):
    return f"{prefix}, {name}!"

print(greet("Alice"))         # Hello, Alice!
print(greet("Bob", "Hi"))     # Hi, Bob!
1
2
3
4
5

字符串、整数、元组等不可变对象不能被修改,每次操作都会创建新对象,不会累积副作用。

# 利用默认参数实现缓存

有些场景下,共享可变默认参数是故意的:

def fibonacci(n, cache={}):
    if n in cache:
        return cache[n]
    if n <= 1:
        return n
    cache[n] = fibonacci(n - 1) + fibonacci(n - 2)
    return cache[n]

print(fibonacci(10))  # 55
print(fibonacci.__defaults__)  # ({0: 0, 1: 1, 2: 1, ...},)
1
2
3
4
5
6
7
8
9
10

cache在所有调用之间共享,避免重复计算。但这种写法容易让阅读代码的人困惑,更推荐用functools.lru_cache装饰器或全局变量。

# 知识拓展

Q1:为什么Python要在定义时求值默认参数?

设计决策。定义时求值让默认参数可以是任意表达式,包括函数调用、类实例化等。如果改成调用时求值,会增加运行时开销,且让默认参数的行为更难预测。

Q2:如何查看函数的默认参数?

访问__defaults__属性:

def example(a, b=10, c=[]):
    pass

print(example.__defaults__)  # (10, [])
1
2
3
4

关键字参数的默认值在__kwdefaults__中。

Q3:传递参数时会覆盖默认值吗?

会。传递参数后,形参绑定到传入的对象,不再使用默认值:

def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

my_list = ["existing"]
result = add_item("new", my_list)
print(result)    # ['existing', 'new']
print(my_list)   # ['existing', 'new'],外部列表被修改
1
2
3
4
5
6
7
8
9
10

Q4:能在定义时就创建多个独立的默认对象吗?

不能。def只执行一次,默认参数对象只创建一次。想要每次调用都得到新对象,只能在函数内创建。

Q5:PyLint或Flake8会检测这个问题吗?

会。静态检查工具会对可变默认参数发出警告(W0102 / dangerous-default-value)。IDE通常也会高亮提示。

Last Updated: 10/13/2026, 5:57:00 PM

← 位置参数、关键字参数、*args和**kwargs怎么用? Python中的匿名函数、map、filter和reduce怎么用? →

评论

验证登录状态...

侧边栏 侧边栏
夜间模式 夜间
卡码简历 卡码简历
代码随想录 代码随想录
卡码投递表 卡码投递表🔥
2026实习校招群 2026群
添加客服微信 2026实习校招客服微信 PS:通过微信后,请发送姓名-学校-年级-2026实习/校招
支持卡码笔记 支持卡码笔记
鼓励/支持/赞赏Carl 卡码笔记赞赏码
1. 如果感觉本站对你很有帮助,也可以请Carl喝杯奶茶,金额大小不重要,心意已经收下
2. 希望大家都能梦想成真,有好的前程,加油💪