第35集:函数参数——可变参数

学习目标

  • 理解可变参数的概念
  • 掌握*args的使用方法
  • 掌握**kwargs的使用方法
  • 学会混合使用各种参数类型

一、什么是可变参数?

可变参数是指可以接受任意数量参数的函数参数。

两种可变参数类型

类型 语法 用途
位置可变参数 *args 接收任意数量的位置参数
关键字可变参数 **kwargs 接收任意数量的关键字参数

生活类比

  • 可变参数就像"无限容量的购物袋"
  • 可以装任意数量的物品
  • 不用提前知道具体数量

二、*args:位置可变参数

基本语法

def 函数名(*args):
    函数体

特点

  • *args在函数内部是一个元组
  • 可以接收任意数量的位置参数
  • 参数名可以是任意名称(通常用args)

示例1:计算任意数的和

def add_all(*args):
    """计算所有数的和"""
    total = sum(args)
    return total

# 可以传递任意数量的参数
result1 = add_all(1, 2, 3)
print(result1)  # 输出:6

result2 = add_all(1, 2, 3, 4, 5, 6)
print(result2)  # 输出:21

result3 = add_all()
print(result3)  # 输出:0

示例2:打印所有参数

def print_args(*args):
    """打印所有参数"""
    print("接收到的参数:")
    for i, arg in enumerate(args, 1):
        print(f"  参数{i}:{arg}")

print_args("苹果", "香蕉", "橙子", "葡萄")
# 输出:
# 接收到的参数:
#   参数1:苹果
#   参数2:香蕉
#   参数3:橙子
#   参数4:葡萄

示例3:寻找最大值

def find_max(*args):
    """找出最大值"""
    if len(args) == 0:
        return None
    return max(args)

print(find_max(3, 1, 4, 1, 5, 9, 2, 6))  # 输出:9
print(find_max(-1, -5, -3))  # 输出:-1

三、**kwargs:关键字可变参数

基本语法

def 函数名(**kwargs):
    函数体

特点

  • **kwargs在函数内部是一个字典
  • 可以接收任意数量的关键字参数
  • 参数名可以是任意名称(通常用kwargs)

示例4:打印所有关键字参数

def print_kwargs(**kwargs):
    """打印所有关键字参数"""
    print("接收到的关键字参数:")
    for key, value in kwargs.items():
        print(f"  {key} = {value}")

print_kwargs(name="小明", age=25, city="北京")
# 输出:
# 接收到的关键字参数:
#   name = 小明
#   age = 25
#   city = 北京

print_kwargs(product="手机", price=2999, stock=100, brand="华为")
# 输出:
# 接收到的关键字参数:
#   product = 手机
#   price = 2999
#   stock = 100
#   brand = 华为

示例5:构建用户信息

def build_profile(**kwargs):
    """构建用户信息"""
    profile = {}
    for key, value in kwargs.items():
        profile[key] = value
    return profile

user1 = build_profile(name="小明", age=25, email="xiaoming@example.com")
print(user1)
# 输出:{'name': '小明', 'age': 25, 'email': 'xiaoming@example.com'}

user2 = build_profile(name="小红", city="上海", hobby="编程")
print(user2)
# 输出:{'name': '小红', 'city': '上海', 'hobby': '编程'}

示例6:配置函数

def configure_system(**kwargs):
    """配置系统"""
    config = {
        "host": "localhost",
        "port": 8080,
        "debug": False,
        "timeout": 30
    }
    
    # 更新配置
    config.update(kwargs)
    
    print("系统配置:")
    for key, value in config.items():
        print(f"  {key} = {value}")
    
    return config

# 使用默认配置
configure_system()

# 覆盖部分配置
configure_system(host="192.168.1.100", port=9000, debug=True)

# 完全自定义配置
configure_system(host="example.com", port=443, timeout=60, ssl=True)

四、混合使用各种参数

参数顺序规则

def func(固定参数, *args, 默认参数, **kwargs):
    函数体

示例7:混合参数使用

def show_info(name, *args, **kwargs):
    """显示信息"""
    print(f"姓名:{name}")
    
    if args:
        print("其他参数(元组):")
        for i, arg in enumerate(args, 1):
            print(f"  参数{i}:{arg}")
    
    if kwargs:
        print("关键字参数(字典):")
        for key, value in kwargs.items():
            print(f"  {key}:{value}")

show_info("小明", 25, "北京", gender="男", hobby="编程")
# 输出:
# 姓名:小明
# 其他参数(元组):
#   参数1:25
#   参数2:北京
# 关键字参数(字典):
#   gender:男
#   hobby:编程

示例8:计算器函数

def calculator(operation, *numbers, precision=2):
    """计算器"""
    result = 0
    
    if operation == "add":
        result = sum(numbers)
    elif operation == "multiply":
        result = 1
        for num in numbers:
            result *= num
    elif operation == "max":
        result = max(numbers) if numbers else 0
    elif operation == "min":
        result = min(numbers) if numbers else 0
    
    return round(result, precision)

# 使用不同的运算
print(calculator("add", 1, 2, 3, 4, 5))  # 输出:15
print(calculator("multiply", 2, 3, 4))  # 输出:24
print(calculator("max", 5, 2, 8, 1, 9))  # 输出:9
print(calculator("min", 5, 2, 8, 1, 9))  # 输出:1
print(calculator("add", 1.111, 2.222, 3.333, precision=3))  # 输出:6.666

五、实际应用案例

案例1:格式化字符串

def format_message(template, *args, **kwargs):
    """格式化消息"""
    try:
        # 使用位置参数
        result = template.format(*args)
        
        # 使用关键字参数替换剩余的占位符
        result = result.format(**kwargs)
        
        return result
    except Exception as e:
        return f"格式化错误:{e}"

# 使用示例
message1 = format_message("你好,{}!", "小明")
print(message1)  # 输出:你好,小明!

message2 = format_message("姓名:{},年龄:{},城市:{}", 
                          "小红", 25, city="上海")
print(message2)  # 输出:姓名:小红,年龄:25,城市:上海

案例2:购物车

def shopping_cart(customer_name, *items, **options):
    """购物车"""
    print(f"客户:{customer_name}")
    print("\n商品清单:")
    
    total = 0
    for i, item in enumerate(items, 1):
        if isinstance(item, dict):
            # 商品是字典格式
            name = item.get("name", "未知")
            price = item.get("price", 0)
            quantity = item.get("quantity", 1)
            subtotal = price * quantity
            total += subtotal
            print(f"  {i}. {name} × {quantity} = ¥{subtotal}")
        else:
            # 简单的商品名称
            print(f"  {i}. {item}")
    
    print(f"\n小计:¥{total}")
    
    # 应用优惠
    discount = options.get("discount", 0)
    if discount > 0:
        discount_amount = total * discount
        total -= discount_amount
        print(f"折扣:-¥{discount_amount:.2f}")
    
    print(f"总计:¥{total:.2f}")

# 使用示例
shopping_cart("小明",
             {"name": "手机", "price": 2999, "quantity": 1},
             {"name": "耳机", "price": 199, "quantity": 2},
             discount=0.1)

shopping_cart("小红",
             "笔记本", "笔", "橡皮擦")

案例3:日志记录器

def log(message, *args, level="INFO", timestamp=True, **metadata):
    """日志记录器"""
    from datetime import datetime
    
    # 构建日志信息
    log_entry = []
    
    # 添加时间戳
    if timestamp:
        time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log_entry.append(f"[{time_str}]")
    
    # 添加日志级别
    log_entry.append(f"[{level}]")
    
    # 添加消息
    if args:
        message = message.format(*args)
    log_entry.append(f" {message}")
    
    # 添加元数据
    if metadata:
        metadata_str = ", ".join(f"{k}={v}" for k, v in metadata.items())
        log_entry.append(f" ({metadata_str})")
    
    # 输出日志
    print("".join(log_entry))

# 使用示例
log("用户 {} 登录成功", "xiaoming", level="INFO")
log("数据库连接失败", level="ERROR", host="localhost", port=5432)
log("订单 {} 创建成功,金额:{}", "ORDER123", 299.99, 
    level="DEBUG", user_id=1001)

六、解包操作

解包列表/元组给*args

def add_all(*args):
    """计算所有数的和"""
    return sum(args)

numbers = [1, 2, 3, 4, 5]
result = add_all(*numbers)  # 解包列表
print(result)  # 输出:15

解包字典给**kwargs

def show_info(**kwargs):
    """显示信息"""
    for key, value in kwargs.items():
        print(f"{key}: {value}")

user_data = {"name": "小明", "age": 25, "city": "北京"}
show_info(**user_data)
# 输出:
# name: 小明
# age: 25
# city: 北京

混合解包

def process_data(name, age, *hobbies, **details):
    """处理数据"""
    print(f"姓名:{name}")
    print(f"年龄:{age}")
    
    if hobbies:
        print("爱好:")
        for hobby in hobbies:
            print(f"  - {hobby}")
    
    if details:
        print("详情:")
        for key, value in details.items():
            print(f"  {key}: {value}")

base_info = ["小明", 25]
extra_hobbies = ["编程", "阅读", "游泳"]
extra_details = {"city": "北京", "job": "工程师"}

process_data(*base_info, *extra_hobbies, **extra_details)

七、可变参数的最佳实践

实践1:合理命名

# ✅ 好的命名
def print_values(*numbers):
    """打印多个数字"""
    print(numbers)

def get_person_info(**person_data):
    """获取人员信息"""
    return person_data

# ❌ 不好的命名(虽然可以工作,但不直观)
def print_values(*a):
    """打印多个数字"""
    print(a)

实践2:提供文档说明

def aggregate_data(operation, *values, **options):
    """
    聚合数据
    
    参数:
        operation (str): 操作类型(sum, avg, max, min)
        *values: 任意数量的数值
        **options: 可选参数
            - precision (int): 精度,默认2
            - verbose (bool): 是否显示详细信息,默认False
    
    返回:
        计算结果
    """
    pass

实践3:参数验证

def calculate_average(*numbers, precision=2):
    """计算平均值"""
    if len(numbers) == 0:
        return 0
    
    # 验证所有参数都是数字
    if not all(isinstance(num, (int, float)) for num in numbers):
        raise ValueError("所有参数必须是数字")
    
    average = sum(numbers) / len(numbers)
    return round(average, precision)

# 使用示例
print(calculate_average(1, 2, 3, 4, 5))  # 输出:3.0
# print(calculate_average(1, 2, "3"))  # 报错

八、常见错误与调试

错误1:*args和**kwargs混用错误

# ❌ 错误:**kwargs必须在*args之后
def func(*args, **kwargs, name):  # 报错
    pass

# ✅ 正确
def func(*args, name, **kwargs):
    pass

错误2:重复定义参数

# ❌ 错误
def func(name, *args, name):  # 报错:name重复定义
    pass

# ✅ 正确
def func(name, *args, age):  # 不同参数名
    pass

调试技巧:打印参数

def debug_func(*args, **kwargs):
    """调试函数"""
    print(f"args 类型:{type(args)}")
    print(f"args 内容:{args}")
    print(f"kwargs 类型:{type(kwargs)}")
    print(f"kwargs 内容:{kwargs}")

debug_func(1, 2, 3, name="小明", age=25)

九、可变参数的优势

优势 说明
灵活性 可以处理任意数量的参数
通用性 适用于各种场景
简化调用 不需要提前知道参数数量
可扩展 易于添加新功能

十、小结

知识点 说明
*args 位置可变参数,内部是元组
**kwargs 关键字可变参数,内部是字典
参数顺序 固定参数 → *args → 默认参数 → **kwargs
解包 使用*和**解包列表和字典
混合使用 可以同时使用多种参数类型

十一、课后练习

练习1

定义函数multiply_all(*args),计算所有数的乘积。

练习2

定义函数build_person(**kwargs),构建人员信息字典。

练习3

定义函数format_report(title, *data, **options),生成报告。

练习4

定义函数find_middle(*args),找出中间位置的数。

练习5

定义函数create_email(to, subject, *cc_list, **headers),创建邮件信息。

« 上一篇 函数参数-关键字参数 下一篇 » 返回值与return语句