讲解

Python 的参数系统灵活到有点复杂,这节一次理清。最基础的是位置参数和关键字参数:调用 func(1, 2) 按位置对应,func(a=1, b=2) 按名字对应——关键字传参可读性更好,顺序也自由。定义时可以给参数默认值:def greet(name, greeting='你好'),调用时不传就用默认值。规则:有默认值的参数必须排在无默认值的后面。

最著名的坑是可变默认值:def f(items=[]) 的默认值列表在函数定义时只创建一次,被所有调用共享——第一次调用 append 的东西,第二次调用还在。正确写法是默认 None,函数体内 items = [] if items is None else items。这个坑每个 Python 程序员都会踩一次,早点记住早解脱。

收集参数处理个数不定的情况:*args 把多余的位置参数收进元组,kwargs 把多余的关键字参数收进字典。def func(a, args, **kwargs) 是最通用的签名。反向操作是调用时用 * 和 ** 展开:func([1, 2]) 等价 func(1, 2),func({'a': 1}) 等价 func(a=1)。另外还有两个进阶标记:参数列表里的 * 之后的参数强制关键字传参(def f(a, *, key)),/ 之前的参数强制位置传参——用来设计更清晰的 API。

示例

# 位置参数、关键字参数、默认值
def introduce(name, city='北京', greeting='你好'):
    return f'{greeting},我是{name},来自{city}'


print(introduce('小明'))
print(introduce('小红', '上海'))
print(introduce('小刚', greeting='嗨'))  # 关键字传参,顺序自由

# 可变默认值的坑 vs 正确写法
def bad_append(item, items=[]):
    items.append(item)
    return items


print('第一次:', bad_append('a'))
print('第二次:', bad_append('b'))  # ['a', 'b'] —— 上次的残留!


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


print('正确写法第一次:', good_append('a'))
print('正确写法第二次:', good_append('b'))  # ['b'] —— 各算各的

# *args 与 **kwargs
def report(title, *scores, **options):
    print(f'标题:{title}')
    print(f'分数们:{scores}(共 {len(scores)} 个)')
    print(f'选项:{options}')


report('月考', 90, 85, 77, show_average=True, language='zh')


# 调用时的展开
def add3(a, b, c):
    return a + b + c


nums = [1, 2, 3]
print('列表展开传参:', add3(*nums))
params = {'a': 10, 'b': 20, 'c': 30}
print('字典展开传参:', add3(**params))

# 强制关键字参数
def create_user(name, *, admin=False):
    return f'用户 {name},管理员:{admin}'


print(create_user('小明', admin=True))
# create_user('小红', True)  # 会报错:admin 必须用关键字传

常见坑

  • 可变默认值共享:def f(x=[]) 是所有 Python 程序员的「成年礼」坑,默认值在 def 执行时创建一次并被共享。默认 None,函数里再造。
  • 默认参数排在普通参数前:def f(a=1, b) 是语法错误。带默认值的必须放后面。
  • 位置参数跟在关键字参数后调用:f(a=1, 2) 是语法错误。调用时一旦用了关键字,后面都得是关键字。
  • **滥用 kwargs 吞掉一切:kwargs 让拼写错误的参数名悄悄通过(如 colour='red' 被吞掉)。能写明的参数就写明,kwargs 只用于真正的开放扩展。

小结

默认值排最后、可变默认用 None 兜底;*args 收位置、*kwargs 收关键字;调用时用 * 和 ** 展开; 之后强制关键字。下一节看一次性小函数:lambda。