Python函数默认参数怎么定义
在 Python 中,定义带有默认值的函数参数非常直观:在声明函数形参时,直接使用赋值符号 = 为该参数指定一个默认值。当调用者没有传入对应实参时,函数就会自动采用该预设值;若传入了实参,则传入的值会覆盖默认值。这种机制能够简化函数调用接口,让常用配置开箱即用,同时保留必要的定制空间。
一、基本语法与位置顺序约束
定义默认参数时,最核心的语法形式是在 def 语句中的形参列表里直接指定取值。例如定义一个格式化打招呼的函数:
def greet(name, greeting="你好"):
return f"{greeting},{name}!"
# 使用默认问候语
print(greet("张三")) # 输出:你好,张三!
# 显式传入自定义问候语
print(greet("李四", "早上好")) # 输出:早上好,李四!
这里有一个在语法层面必须遵守的硬性规则:所有带有默认值的形参必须排在没有默认值的形参后面。如果在非默认参数前面声明了默认参数,Python 解释器在编译代码时会直接抛出 SyntaxError: non-default argument follows default argument 异常。
# 错误写法:默认参数放在必选参数前
def create_profile(role="user", username):
pass
# 触发 SyntaxError: non-default argument follows default argument
# 正确写法:必选参数在前,默认参数在后
def create_profile(username, role="user"):
pass
产生这一限制的原因在于 Python 处理位置参数的绑定逻辑。当调用函数时,如果按位置依次传入实参,解释器无法明确判断一个参数究竟对应前面的默认项还是后面的必填项。严格保持“必选在前、默认在后”的排布顺序,消除了调用时的语义歧义。
二、默认参数的求值时机与可变对象陷阱
初学者在使用 Python 默认参数时最常遭遇的问题,是把列表(list)、字典(dict)或集合(set)等可变类型作为默认值。下面这段看似普通的代码通常会导致令人困惑的行为:
def add_student(name, student_list=[]):
student_list.append(name)
return student_list
print(add_student("小明")) # 输出:['小明']
print(add_student("小红")) # 输出:['小明', '小红'],而不是预期的 ['小红']
print(add_student("小刚")) # 输出:['小明', '小红', '小刚']
每次调用如果没有传入第二个参数,函数内部的列表似乎在持续累加之前调用的数据。这是因为:Python 的默认参数表达式仅在模块加载、函数被定义的那一刻求值一次,而不是在每次调用函数时重新求值。
当 Python 执行 def 语句时,解释器在内存中创建了一个空列表对象,并将这个对象的内存引用保存在函数对象的内部属性 __defaults__ 中。每次在不传参数的情况下调用该函数,形参 student_list 都会指向同一个已存在的列表对象。在函数体内调用 append 修改列表时,改变的是这个被共享的同一块内存。
# 检查函数的 __defaults__ 属性
print(add_student.__defaults__) # 输出:(['小明', '小红', '小刚'],)
三、推荐解决方案:None 哨兵模式
在 Python 社区中,处理可变默认参数的标准工程实践是采用 None 哨兵模式:在定义函数时,将参数的默认值设为不可变的 None;在函数体内部通过条件语句判断该形参是否为 None,如果是,再动态创建一个全新的容器对象。
def add_student(name, student_list=None):
# 动态初始化全新列表
if student_list is None:
student_list = []
student_list.append(name)
return student_list
# 每次未传参调用,都会获得一个独立全新的列表
print(add_student("小明")) # 输出:['小明']
print(add_student("小红")) # 输出:['小红']
# 若调用者明确传入了现有列表,则在该列表上操作
my_class = ["小华"]
add_student("小刚", my_class)
print(my_class) # 输出:['小华', '小刚']
这种模式具有明确的优势:
- 隔离生命周期:
None是不可变的单例对象,即使被多次赋值也不会发生内部修改;新容器仅在函数被调用时按需创建,保证每次调用拥有完全独立的运行环境。 - 意图清晰:调用者可以清晰地通过文档或类型标注获知该参数是可选的容器。
- 兼顾灵活性:当外部调用者需要传入已有列表进行累积操作时,函数能够正常接收并复用该对象。
四、进阶用法与工程规范
1. 仅限关键字的默认参数 (Keyword-only Arguments)
在 Python 3 中,可以使用单个星号 * 作为分隔符,将其后面的所有参数声明为仅限关键字参数。这种写法非常适合具有较多可选配置项的函数,强制调用者必须显式指明参数名,从而提高代码可读性,避免因位置错误传入配置。
def connect_db(host, port=3306, *, timeout=10, retry=3):
# host 与 port 可按位置传入
# timeout 与 retry 必须使用关键字传入
print(f"连接到 {host}:{port},超时:{timeout}秒,重试:{retry}次")
# 正确调用
connect_db("localhost", 5432, timeout=5)
# 错误调用:如果尝试通过位置传参给 timeout,会抛出 TypeError
# connect_db("localhost", 5432, 5)
# TypeError: connect_db() takes from 1 to 2 positional arguments but 3 were given
2. 配合类型注解 (Type Hints)
在现代 Python 代码中,推荐为带有默认值的参数添加类型注解。语法规范为:在参数名后紧跟 : 类型,然后用等号指定默认值。注意在 PEP 8 风格指南中,当存在类型注解时,等号两边保留空格;当没有类型注解时,等号两边不加空格。
from typing import Optional
# 规范写法:类型注解与默认值并存
def query_records(
table_name: str,
limit: int = 100,
filters: Optional[dict] = None
) -> list:
if filters is None:
filters = {}
return [table_name, limit, filters]
代码风格提示:根据 PEP 8 编码规范,当定义纯无类型注解的默认参数时,赋值符号两侧不要留空格(例如 def func(a=1):);但在使用类型注解时,赋值符号两侧应当各保留一个空格(例如 def func(a: int = 1):)。
五、总结
定义 Python 函数默认参数的核心要点可以总结为三条:
- 语法位置:带默认值的参数必须跟在所有无默认值的必选参数之后,否则将触发语法解析错误。
- 类型安全:默认参数只在函数定义时执行一次计算。默认值应优先选择数值、字符串、布尔值或元组等不可变对象;对于列表、字典等可变容器,统一声明为
None,并在函数内部动态初始化。 - 接口设计:合理利用星号
*限定关键字传参,并结合类型注解,能够有效提高函数调用的可维护性与健壮性。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













