*args和*kwargs怎么用?

访客 python案例 1

本文目录导读:

  1. 目录导读
  2. 什么是*args和**kwargs?
  3. 为什么需要它们?
  4. *args的详细用法与实战
  5. **kwargs的详细用法与实战
  6. 两者组合使用技巧
  7. 在类继承和装饰器中的应用
  8. 常见错误与避坑指南
  9. 问答环节:你问我答

Python进阶必知:*args和**kwargs的终极用法指南(附10个实战案例)

目录导读

  1. 什么是*args和**kwargs?
  2. 为什么需要它们?
  3. *args的详细用法与实战
  4. **kwargs的详细用法与实战
  5. 两者组合使用技巧
  6. 在类继承和装饰器中的应用
  7. 常见错误与避坑指南
  8. 问答环节:你问我答

什么是*args和**kwargs?

在Python中,*args**kwargs是两种特殊的语法糖,用于处理可变数量参数,它们并不是关键字,只是一种约定俗成的命名习惯(你完全可以写成*params**kws),核心功能是:

  • *args:接收任意数量的位置参数,打包成元组(tuple)
  • **kwargs:接收任意数量的关键字参数,打包成字典(dict)

举个例子:

def demo(*args, **kwargs):
    print(f"位置参数: {args}")  # 输出: (1, 2, 3)
    print(f"关键字参数: {kwargs}")  # 输出: {'name': 'Alice', 'age': 25}
demo(1, 2, 3, name='Alice', age=25)

为什么需要它们?

在实际开发中,你可能会遇到以下场景:

  • 函数适配器模式:需要包装一个函数,但不清楚原函数有多少参数
  • 类继承中的方法重写:子类需要传递父类构造函数的所有参数
  • 装饰器:需要保持被装饰函数的签名灵活性
  • 数据序列化:如JSON解析时动态接收字段

数据对比: 不使用args时,定义一个支持任意数量数字求和的函数需要写多个重载版本,而使用args只需一行代码。

# 糟糕的做法:写死参数个数
def sum_three(a, b, c): return a+b+c
# 优雅的做法:支持任意数量
def sum_all(*args): return sum(args)

*args的详细用法与实战

1 基础用法:接收任意位置参数

def multiply(*numbers):
    result = 1
    for num in numbers:
        result *= num
    return result
print(multiply(2, 3, 4))  # 24

2 解包序列:列表/元组转位置参数

当我们需要将列表元素作为独立参数传递时,使用星号解包:

numbers = [2, 3, 5]
print(multiply(*numbers))  # 等价于 multiply(2,3,5)

3 在lambda中的陷阱

注意:lambda表达式不能直接使用*args?实际上可以:

sum_all = lambda *args: sum(args)
print(sum_all(1,2,3,4))  # 10

4 强制使用关键字参数(Python 3特性)

在*args后的参数必须使用关键字传递:

def greet(greeting, *names, punctuation='!'):
    for name in names:
        print(f"{greeting}, {name}{punctuation}")
greet("Hello", "Alice", "Bob", punctuation="?")  
# 打印: Hello, Alice?  Hello, Bob?

**kwargs的详细用法与实战

1 自由关键字参数接收

常用于配置类函数:

def connect_db(**config):
    host = config.get('host', 'localhost')
    port = config.get('port', 3306)
    user = config.get('user', 'root')
    print(f"连接到 {host}:{port} 作为 {user}")
connect_db(host='10.0.0.1', user='admin', port=5432)

2 字典解包调用函数

当已有字典需要作为关键字参数传递时:

config_dict = {'host': 'test.com', 'port': 8080}
connect_db(**config_dict)

3 合并字典

使用**可以优雅合并多个字典:

defaults = {'color': 'red', 'size': 'M'}
user_input = {'size': 'L', 'material': 'cotton'}
merged = {**defaults, **user_input}  # {'color': 'red', 'size': 'L', 'material': 'cotton'}

4 防止意外参数传入

使用**kwargs收集多余参数,并做校验:

def create_user(name, **kwargs):
    allowed = {'age', 'email', 'phone'}
    extra = set(kwargs) - allowed
    if extra:
        raise TypeError(f"不支持的参数: {extra}")
    # 继续处理...

两者组合使用技巧

1 标准参数顺序

Python函数参数顺序固定为:普通参数 → *args → 关键字默认参数 → **kwargs

def func(a, b=2, *args, c=3, **kwargs):
    pass

2 实战:通用日志器

def logger(level='INFO', *messages, **context):
    timestamp = datetime.now()
    msg = ' '.join(str(m) for m in messages) if messages else ''
    ctx = ', '.join(f"{k}={v}" for k,v in context.items())
    print(f"[{level}] {timestamp}: {msg} | {ctx}")
logger("WARN", "内存使用率过高", user_id=123, cpu=85)  
# [WARN] 2025-03-21: 内存使用率过高 | user_id=123, cpu=85

在类继承和装饰器中的应用

1 子类父类参数传递

class Vehicle:
    def __init__(self, brand, model, **kwargs):
        self.brand = brand
        self.model = model
        self.kwargs = kwargs
class Car(Vehicle):
    def __init__(self, *args, doors=4, **kwargs):
        super().__init__(*args, **kwargs)
        self.doors = doors
my_car = Car('Toyota', 'Camry', doors=2, color='black', seats=5)
print(my_car.kwargs)  # {'color': 'black', 'seats': 5}

2 装饰器完美保留签名

import functools
def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}({args}, {kwargs})")
        return func(*args, **kwargs)
    return wrapper
@log_calls
def add(x, y): return x+y
add(3, y=5)  # 打印: 调用 add((3,), {'y': 5})

常见错误与避坑指南

错误1:忘记解包

def func(a, b, c): pass
arg_list = [1, 2, 3]
func(arg_list)  # TypeError: missing 2 required positional arguments
func(*arg_list)  # 正确

错误2:**kwargs中的键名冲突

当kwargs中有和普通参数同名的键时,会引发TypeError:

def test(a, **kwargs): pass
test(1, a=2)  # TypeError: got multiple values for argument 'a'

错误3:性能考量

频繁使用*args/**kwargs会略慢于固定参数,因为涉及打包/解包操作,性能敏感场景建议明确参数。

错误4:类型注解困扰

# 正确注解方式
from typing import Any, Dict, Tuple
def func(*args: int, **kwargs: str) -> Tuple[int, Dict[str, str]]:
    return sum(args), kwargs

问答环节:你问我答

Q1: *args和**kwargs可以同时省略不写吗?

A: 可以,但如果你需要处理可变参数,就必须使用,不过最佳实践是:直到你确实需要时才使用,否则明确写出参数名称让代码更清晰。

Q2: 为什么我不能在普通参数后直接写*kwargs,中间不用args?

A: Python语法要求*args必须出现在*kwargs之前,否则会报语法错误,因为args收集剩余位置参数,**kwargs收集剩余关键字参数,顺序必须明确。

Q3: 在实战中,我该优先使用*args还是**kwargs?

A: 这取决于你的应用场景:

  • 当参数顺序重要且数量不定时(如数学运算)→ 用*args
  • 当参数名称重要且可能扩展时(如配置参数)→ 用**kwargs
  • 更多见的是两者结合使用,比如requests.get(url, **kwargs)

Q4: 使用*args/**kwargs会影响代码的可读性吗?

A: 确实存在风险,建议:只在函数签名上使用,并在文档字符串中说明预期的参数模式,复杂的函数可考虑使用dataclassattrs库替代。

Q5: 在类方法中,*args和**kwargs如何与self/其他实例方法配合?

A: 完全兼容,典型场景:__init__方法中使用*args接收父类需要的额外参数,而self始终放在最前面:

class Child(Parent):
    def __init__(self, *args, extra1=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.extra1 = extra1

*args和kwargs是Python灵活性的核心工具,掌握它们的本质——解包与打包,能让你写出更具扩展性的代码。过度使用它们会降低代码可读性,明智的做法是在框架设计、装饰器、继承等需要通用性的场景中使用**。

延伸学习: 可以进一步研究Python的inspect模块,它能帮你分析函数签名,配合*args/**kwargs实现更强大的动态调用功能。

标签: args kwargs

抱歉,评论功能暂时关闭!