看到 *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负责处理关键字参数,打包成 字典。
下次看到*和**,不要再死记硬背了。记住:单星号打包元组,双星号打包字典,反过来就是解包。 就这么简单!