Skip to content

参数模型:位置、关键字、解包与默认值陷阱 ​

可变数量参数:*args ​

使用 *args 接收任意数量的位置参数:

python
def sum_all(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(sum_all(1, 2, 3))        # 6
print(sum_all(1, 2, 3, 4, 5))  # 15
print(sum_all())               # 0

*numbers 将所有位置参数收集到元组中:

python
def show_args(*args):
    print(type(args))  # <class 'tuple'>
    print(args)

show_args(1, 2, 3)  # (1, 2, 3)

混合使用 ​

*args 必须在普通位置参数之后,关键字参数之前:

python
def func(a, b, *args, c=10):
    print(f"a={a}, b={b}, args={args}, c={c}")

func(1, 2, 3, 4, 5, c=20)
# a=1, b=2, args=(3, 4, 5), c=20

可变数量关键字参数:**kwargs ​

使用 **kwargs 接收任意数量的关键字参数:

python
def print_info(**info):
    for key, value in info.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="NYC")
# name: Alice
# age: 30
# city: NYC

**info 将所有关键字参数收集到字典中:

python
def show_kwargs(**kwargs):
    print(type(kwargs))  # <class 'dict'>
    print(kwargs)

show_kwargs(x=1, y=2)  # {'x': 1, 'y': 2}

*args 和 **kwargs 组合 ​

python
def flexible_func(a, b, *args, c=10, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"c={c}")
    print(f"kwargs={kwargs}")

flexible_func(1, 2, 3, 4, c=5, x=10, y=20)
# a=1, b=2
# args=(3, 4)
# c=5
# kwargs={'x': 10, 'y': 20}

参数解包:调用时的 * 和 ** ​

解包序列:* ​

python
def add(a, b, c):
    return a + b + c

numbers = [1, 2, 3]
print(add(*numbers))  # 6,等价于 add(1, 2, 3)

# 解包任何可迭代对象
print(add(*range(3)))  # 3,等价于 add(0, 1, 2)

解包字典:** ​

python
def greet(name, age):
    print(f"{name} is {age} years old")

person = {'name': 'Alice', 'age': 30}
greet(**person)  # Alice is 30 years old

组合解包 ​

python
def func(a, b, c, d, e):
    return a + b + c + d + e

print(func(1, 2, *(3, 4), e=5))  # 15
print(func(*[1, 2], c=3, **{'d': 4, 'e': 5}))  # 15

仅限位置参数:/ ​

Python 3.8+ 支持 / 标记,左侧参数只能通过位置传递:

python
def divide(a, b, /):
    return a / b

print(divide(10, 2))  # 5.0
# print(divide(a=10, b=2))  # TypeError

用途:

  • 强制位置传递,避免关键字调用
  • 允许未来改变参数名而不破坏 API
python
def func(pos_only, /, pos_or_kw, *, kw_only):
    print(pos_only, pos_or_kw, kw_only)

func(1, 2, kw_only=3)  # 正确
func(1, pos_or_kw=2, kw_only=3)  # 正确
# func(pos_only=1, pos_or_kw=2, kw_only=3)  # TypeError

仅限关键字参数:* ​

* 后的参数只能通过关键字传递:

python
def greet(name, *, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")  # Hello, Alice!
greet("Bob", greeting="Hi")  # Hi, Bob!
# greet("Carol", "Hey")  # TypeError

用途:

  • 强制关键字传递,提高可读性
  • 避免参数顺序错误
python
def create_user(username, *, email, age, city="Unknown"):
    return {'username': username, 'email': email, 'age': age, 'city': city}

user = create_user("alice", email="alice@example.com", age=30)
# create_user("alice", "alice@example.com", 30)  # TypeError

完整参数顺序 ​

Python 函数参数的完整顺序:

  1. 仅限位置参数(/ 之前)
  2. 普通参数(可位置可关键字)
  3. *args(可变位置参数)
  4. 仅限关键字参数(* 之后或 *args 之后)
  5. **kwargs(可变关键字参数)
python
def complete(pos1, pos2, /, pos_or_kw, *args, kw1, kw2=2, **kwargs):
    print(f"pos1={pos1}, pos2={pos2}")
    print(f"pos_or_kw={pos_or_kw}")
    print(f"args={args}")
    print(f"kw1={kw1}, kw2={kw2}")
    print(f"kwargs={kwargs}")

complete(1, 2, 3, 4, 5, kw1=6, kw2=7, x=8, y=9)
# pos1=1, pos2=2
# pos_or_kw=3
# args=(4, 5)
# kw1=6, kw2=7
# kwargs={'x': 8, 'y': 9}

默认参数陷阱详解 ​

陷阱:可变默认值 ​

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

list1 = add_item("apple")
list2 = add_item("banana")
print(list1)  # ['apple', 'banana'],不是 ['apple']!
print(list2)  # ['apple', 'banana']
print(list1 is list2)  # True

原因:默认值 [] 在函数定义时创建一次,所有调用共享。

修复:

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

list1 = add_item("apple")
list2 = add_item("banana")
print(list1)  # ['apple']
print(list2)  # ['banana']

陷阱:默认值中的时间 ​

python
import time

def log_message(message, timestamp=time.time()):
    print(f"[{timestamp}] {message}")

log_message("First")
time.sleep(2)
log_message("Second")  # 时间戳相同!

修复:

python
def log_message(message, timestamp=None):
    if timestamp is None:
        timestamp = time.time()
    print(f"[{timestamp}] {message}")

陷阱:默认值引用类属性 ​

python
class Config:
    DEBUG = True

def func(debug=Config.DEBUG):
    print(debug)

func()  # True
Config.DEBUG = False
func()  # 仍然是 True(默认值在定义时绑定)

实用技巧 ​

1. 包装任意参数的装饰器 ​

python
def trace(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        return func(*args, **kwargs)
    return wrapper

@trace
def add(a, b):
    return a + b

print(add(3, 5))
# Calling add with args=(3, 5), kwargs={}
# 8

2. 强制关键字参数提高可读性 ​

python
def connect(*, host, port, timeout=30):
    print(f"Connecting to {host}:{port} (timeout={timeout}s)")

connect(host="example.com", port=443)
# connect("example.com", 443)  # TypeError,必须用关键字

3. 合并字典 ​

python
def merge_dicts(*dicts):
    result = {}
    for d in dicts:
        result.update(d)
    return result

# 或使用解包(Python 3.5+)
def merge_dicts(*dicts):
    return {k: v for d in dicts for k, v in d.items()}

d1 = {'a': 1, 'b': 2}
d2 = {'b': 3, 'c': 4}
print(merge_dicts(d1, d2))  # {'a': 1, 'b': 3, 'c': 4}

练习 ​

  1. 可变参数求和:编写 multiply_all(*numbers),返回所有数字的乘积。处理空参数情况(返回 1)。

  2. 字典合并:编写 merge(**dicts),接收任意数量的关键字参数,返回合并后的字典。后面的值覆盖前面的。

  3. 参数转发:编写装饰器 @log_calls,打印函数调用的所有参数(位置和关键字),然后调用原函数。

  4. 修复默认值陷阱:修复以下函数:

    python
    def create_student(name, courses=[]):
        courses.append("Math")
        return {'name': name, 'courses': courses}
    
    alice = create_student("Alice")
    bob = create_student("Bob")
    print(alice)  # 期望只有 Math
    print(bob)    # 期望只有 Math
  5. 灵活的配置函数:编写 configure(required_param, /, optional_param=10, *, flag=False, **extra),演示所有参数类型的组合使用。

完成练习后,打开本章答案页核对。

本章小结 ​

第二章建立了函数的完整认识:

  • 函数定义、参数、返回值
  • LEGB 作用域规则
  • 闭包捕获外层变量
  • 灵活的参数模型(*args, **kwargs, /, *)

这些是 Python 函数式编程的基础。第三章将学习对象、集合与文本数据。

回到第二章概览 · 返回Python 学习路线

用心记录每一步成长