别再死记硬背了!带你扒开*args和**kwargs的底裤

看到 *args和 **kwargs 这两个东西时一头雾水?不禁脑子里想:这到底是个啥呀,为甚要写它呢?教程都是告诉你怎么用,但就是不告诉你为什么这么用。今天咱们就抛开那些官方辞令,一起扒开它们的底裤,看看里面到底藏着什么秘密!

从两个小烦恼说起

想象一下,你要写一个加法函数。你说:这个简单,哐哐哐一顿输出写出了如下的代码:

python 复制代码
def add(a, b):
    return a + b

后来需求变了,需要三个数相加,你说:这个简单再加一个参数完事儿:

python 复制代码
def add(a, b, c):
    return a + b + c

如果还要支持四个数、五个数相加呢?这时候你就遇到第一个烦恼:参数个数不确定怎么办?

再来看第二个烦恼。假如你要写个用户注册函数呢?:

python 复制代码
def register(name, email, age, address, phone):
    # 注册逻辑
    pass

调用的时候是这样的:

python 复制代码
register("张三", "zhangsan@example.com", 25, "北京市海淀区", "13800138000")

这么多参数,顺序记错了怎么办?参数含义不清晰怎么办?这时候你就需要一种能够明确标识参数含义的方式。

*args:其实就是个打包工

解决第一个烦恼的就是*args。它的本质很简单:就是把多个位置参数打包成一个元组(tuple)

python 复制代码
def add(*numbers):
    print(type(numbers))  # <class 'tuple'>
    total = 0
    for num in numbers:
        total += num
    return total

当你调用add(1, 2, 3, 4)时,Python悄悄做了件事:把1, 2, 3, 4打包成元组(1, 2, 3, 4),然后传给numbers参数。

星号*就像是告诉Python:"帮我把这些零散的位置参数都收集起来,打包成元组!"

这时候你可能会问:为什么非要叫args?叫别的行不行?答案是:完全可以!args只是约定俗成的名字,你写成numbers、params甚至 whatever都行。关键是那个星号,它才是真正的魔法符号。可千万不敢忘了前面那个星号,不然的话,意义就不一样了。说到这里总该明白了吧!

**kwargs:给参数贴上标签

解决第二个烦恼的是**kwargs。它的工作也很明确:把关键字参数打包成一个字典(dict)。

python 复制代码
def register(**user_info):
    print(type(user_info))  # <class 'dict'>
    print(user_info)

调用的时候可以这样:

python 复制代码
register(name="张三", email="zhangsan@example.com", age=25)

Python会把这些关键字参数打包成字典:{"name": "张三", "email": "zhangsan@example.com", "age": 25}

双星号**就像是在说:"把这些带标签的参数都收集起来,打包成字典!"

同样,kwargs也不是必须的,你可以用**info、data等其他名字。关键是双星号

底层原理:参数的解构

其实*和**有两个作用:打包和解包。

打包我们已经说了,就是把多个参数打包成元组或字典。解包则正好相反:

python 复制代码
def show_args(a, b, c):
    print(a, b, c)
    
args = (1, 2, 3)
show_args(*args)  # 相当于 show_args(1, 2, 3)

kwargs = {"a": 1, "b": 2, "c": 3}
show_args(**kwargs)  # 相当于 show_args(a=1, b=2, c=3)

这种解包特性在Python中广泛应用,比如合并列表或字典:

python 复制代码
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = [*list1, *list2]  # [1, 2, 3, 4, 5, 6]

dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = {**dict1, **dict2}  # {"a": 1, "b": 2, "c": 3, "d": 4}

实际应用

知道了原理,我们来看看它们在实际开发中的妙用。

1. 装饰器:不改变函数本身增强功能

装饰器是Python的一大特色,而*args和**kwargs让装饰器能够处理任何函数:

python 复制代码
def log_time(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__}执行时间: {end - start}秒")
        return result
    return wrapper

@log_time
def slow_function():
    time.sleep(1)
    
@log_time
def add(*numbers):
    return sum(numbers)

同一个装饰器,既能装饰无参数的函数,也能装饰有参数的函数。

2. 继承

在面向对象编程中,我们经常需要扩展父类方法:

python 复制代码
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Dog(Animal):
    def __init__(self, breed, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.breed = breed

# 使用
my_dog = Dog(breed="金毛", name="阿黄", age=2)

这样即使Animal类的初始化参数发生变化,Dog类也不需要修改。

3. API设计:灵活处理参数

写第三方库或者框架时,经常需要向下兼容:

python 复制代码
def connect_to_database(host, port=3306, **kwargs):
    # 必需的host参数
    # 可选的port参数
    # 其他可选参数通过kwargs传递
    print(f"连接到 {host}:{port}")
    if 'timeout' in kwargs:
        print(f"超时时间: {kwargs['timeout']}")
    
# 新旧调用方式都支持
connect_to_database("localhost")
connect_to_database("localhost", 3307)
connect_to_database("localhost", timeout=10)
connect_to_database("localhost", 3307, timeout=10, retry=3)

切记切记!!!

虽然*args和**kwargs很强大,但也要注意以下几点:

  • 顺序很重要:在函数定义中,必须是普通参数、*args、**kwargs这个顺序
  • 不要滥用:如果参数明确且固定,直接写明确参数名更清晰

总结

这下总该知道*args和**kwargs了吧!它们只是Python提供的参数打包和解包工具。*args负责处理不确定数量的位置参数,打包成 元组 ;**kwargs负责处理关键字参数,打包成 字典

下次看到*和**,不要再死记硬背了。记住:单星号打包元组,双星号打包字典,反过来就是解包。 就这么简单!

相关推荐
printfall1 小时前
src/cli/run-main.ts
后端
m0_743297421 小时前
Python在金融科技(FinTech)中的应用
jvm·数据库·python
老师好,我是刘同学1 小时前
列表推导式详解与实战应用
python
与虾牵手2 小时前
Rust 入门:一个写了 6 年 Python 的人,被编译器骂了三天
python
2401_857865232 小时前
Python日志记录(Logging)最佳实践
jvm·数据库·python
AsDuang2 小时前
Python 3.12 MagicMethods - 54 - __rrshift__
开发语言·python
阳火锅2 小时前
34岁前端倒计时:老板用AI手搓系统那天,我知道我的“体面退休”是个笑话
前端·后端·程序员
姓王者2 小时前
# 解决 Nautilus 自定义终端插件安装依赖问题
前端·后端·全栈
白太岁2 小时前
Redis:缓存、集群、优化与数据结构
redis·后端