在Python中定义函数时,给形参设置默认值能让调用方省去不必要的传参工作,接口也变得更灵活。语法本身很简单,在参数列表里用等号给参数赋一个初始值即可,但默认值的求值时机、参数排列顺序以及可变默认值带来的隐患,都有值得深入理解的地方。本文从基础语法讲起,逐步展开这些细节。

一、设置默认值的基本语法
定义函数时,在形参后面直接跟上=和一个值,这个值就成为该参数的默认值。调用函数时如果没有传入这个参数,函数就会使用默认值;如果传了,就用传入的值覆盖默认值。
def greet(name, greeting="你好"):
print(greeting + ", " + name)
greet("小明") # 输出:你好, 小明
greet("小红", "早上好") # 输出:早上好, 小红</code>上面的例子中,greeting就是带默认值的参数。第一次调用只传了一个位置参数,greeting自动取默认值“你好”;第二次调用显式传入了“早上好”,默认值被覆盖。
默认值可以设置为函数返回值或表达式,比如def foo(x, y=2*3)是完全合法的。但要注意,这个表达式只在函数定义那一刻被求值一次,而不是每次调用时都重新求值,这一点在后面会详细展开。
二、参数的排列顺序有严格要求
Python规定:带默认值的参数必须放在不带默认值的参数(必选参数)之后。如果顺序写反了,解释器在语法解析阶段就会直接报错。
# 错误写法:默认参数放在了必选参数前面
def bad(a=1, b):
pass
# SyntaxError: non-default argument follows default argument
# 正确写法
def good(a, b=1):
pass完整的参数顺序规则是:位置参数 → 默认参数 → 可变参数(*args)→ 关键字参数 → 关键字可变参数(**kwargs)。举个例子:
def func(a, b=10, *args, key, **kwargs):
print(a, b, args, key, kwargs)
func(1, 2, 3, 4, key="k", x=5, y=6)
# 输出:1 2 (3, 4) k {'x': 5, 'y': 6}这样设计的原因很好理解:如果默认参数放在前面,调用时用位置传参,解释器就无法判断后面的实参到底对应谁。牢记这个顺序,可以避免大部分函数定义时的语法错误。
三、可变默认值是最大的坑
默认值最容易出问题的地方,是把列表、字典、集合这类可变对象设为默认值。看一个经典例子:
def add_item(item, items=[]):
items.append(item)
return items
print(add_item(1)) # 输出:[1]
print(add_item(2)) # 输出:[1, 2]
print(add_item(3)) # 输出:[1, 2, 3]很多人期望每次调用都得到一个新列表,但实际输出却是累积的结果。原因在于:默认值在函数定义时只求值一次,这个[]被绑定到了函数对象的一个属性上,之后所有调用共享同一个列表对象。
可以用add_item.__defaults__查看函数的默认值,你会发现它保存的就是那个被反复修改的列表。这从底层证实了默认值与函数对象绑定,而不是与某次调用绑定。
正确的做法是用None作为哨兵值,在函数体内判断后再创建新对象:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
print(add_item(1)) # 输出:[1]
print(add_item(2)) # 输出:[2]注意这里应该用is None判断,而不是items == None或if not items。因为如果调用方传入了一个空列表,if not items会误判,导致传入的列表被丢弃。这是一个容易被忽视的细节。
四、默认值的其他实用技巧
默认值配合关键字调用可以让代码可读性大幅提升,尤其在参数较多时,明确写出参数名能避免传错顺序:
def create_user(name, age=18, city="北京", vip=False):
print(name, age, city, vip)
create_user("张三", city="上海", vip=True) # 跳过age只传city和vip另外,默认值可以引用前面已经出现的参数,这能实现一种依赖参数的写法:
def report(width, height, area=width * height):
return area不过这种写法在Python中并不支持(会报NameError),因为在函数定义时参数名还未在可访问的作用域内。如果需要基于其他参数计算默认值,同样要借助None哨兵,在函数体内处理。
还有一种场景是需要区分“调用方没传”和“调用方传了None”。此时可以定义一个模块级的哨兵对象,比如_MISSING = object(),然后判断if x is _MISSING,这样即使调用方显式传None也能被正确识别。
总结
给Python函数的形参设置默认值,核心语法是def f(a, b=默认值),记住三点就不会踩坑:一是默认参数必须放在必选参数之后;二是默认值只在定义时求值一次,可变对象作默认值会被共享,务必用None哨兵值代替;三是函数体内判断时用is None,既准确又符合Python的惯用写法。掌握这些规则后,默认参数会成为你设计函数接口时非常好用的工具。