这问题问得好,确实,Python 里这两个小东西,`` 和 ``,看似简单,但它们的能耐可大了去,尤其是用在函数定义和调用上,更是能让你的代码变得灵活又强大。咱们这就来捋一捋,把它们说透了。
`args`:收集“散弹”传进来的位置参数
想象一下,你写一个函数,本意是想接收几个固定的参数,比如 `def my_func(a, b):`。但有时候,你可能不知道用户会传进来几个参数,或者你压根就想让这个函数变得更“海纳百川”。这时,`args` 就派上用场了。
内涵是什么?
当你在函数定义时,在某个参数前加上一个 ``(通常习惯写成 `args`,这里的 `args` 是 argument 的缩写,你也可以改成别的名字,比如 `params`,但 `args` 是约定俗成,一看就明白),它就像一个“捕手”,会把所有多余的、没有被其他明确参数接收到的位置参数一股脑儿地收集起来。
它收集起来的是什么?
收集起来的,不是单个的参数,而是一个元组(tuple)。没错,就是一个元组,里面装着所有那些“散弹”式传进来的参数。
怎么理解?
我们来看个例子:
```python
def my_sum(numbers):
total = 0
for num in numbers:
total += num
print(f"接收到的参数是:{numbers}")
return total
调用演示
print(my_sum(1, 2, 3))
输出:
接收到的参数是:(1, 2, 3)
6
print(my_sum(10, 20, 30, 40, 50))
输出:
接收到的参数是:(10, 20, 30, 40, 50)
150
print(my_sum())
输出:
接收到的参数是:()
0
```
在这个 `my_sum` 函数里,我们只定义了 `numbers`。当你调用 `my_sum(1, 2, 3)` 时,`1, 2, 3` 这三个值就构成了一个元组 `(1, 2, 3)`,然后赋值给了 `numbers`。函数内部就可以像操作普通元组一样,遍历它求和。
`args` 的位置很重要
`args` 必须放在所有普通位置参数之后。比如:
```python
def example_func(arg1, arg2, rest):
print(f"第一个参数: {arg1}")
print(f"第二个参数: {arg2}")
print(f"剩余参数: {rest}")
example_func('a', 'b', 'c', 'd', 'e')
输出:
第一个参数: a
第二个参数: b
剩余参数: ('c', 'd', 'e')
```
如果 `args` 放在前面,Python 就不知道哪些是给 `args` 的,哪些是给后面的普通参数的,会报错。
应用场景:
处理不确定数量的参数: 就像上面的求和例子,或者一个打印日志的函数,可以接受任意数量的要打印的内容。
函数装饰器: 装饰器经常需要包装被装饰的函数,而不知道被装饰函数具体接收多少个参数,`args` 就能派上用场,将参数原封不动地传递给被装饰的函数。
函数调用时的“解包”: `` 也可以用在函数调用时,把一个可迭代对象(如列表、元组)的元素“解包”成独立的参数传递进去。
```python
my_list = [5, 6, 7]
print(my_sum(my_list)) 等同于 my_sum(5, 6, 7)
输出:
接收到的参数是:(5, 6, 7)
18
```
看到没?这个 `` 在调用时,就把 `my_list` 里面的 `5, 6, 7` 分别“拆开”,变成了 `my_sum(5, 6, 7)`。
`kwargs`:收集“键值对”传进来的关键字参数
紧接着 `args`,咱们来看看 `kwargs`。如果说 `args` 是收集“散弹”,那 `kwargs` 就是专门用来收集“装箱”的“包裹”——那些以“键=值”形式传进来的参数。
内涵是什么?
在函数定义时,你会在某个参数前加上两个星号 ``(通常习惯写成 `kwargs`,这里的 `kwargs` 是 keyword argument 的缩写,同样可以换名字,但 `kwargs` 是惯例)。它的作用是收集所有多余的、没有被其他明确参数接收到的关键字参数。
它收集起来的是什么?
收集起来的,是一个字典(dictionary)。这个字典的键(key)就是用户传递进来的参数名,值(value)就是对应的参数值。
怎么理解?
还是用例子说话:
```python
def display_info(name, details):
print(f"姓名: {name}")
print("额外信息:")
for key, value in details.items():
print(f" {key}: {value}")
调用演示
display_info("张三", age=25, city="北京", job="工程师")
输出:
姓名: 张三
额外信息:
age: 25
city: 北京
job: 工程师
display_info("李四", country="中国")
输出:
姓名: 李四
额外信息:
country: 中国
display_info("王五") 没有额外信息
输出:
姓名: 王五
额外信息:
```
在这个 `display_info` 函数里,`name` 是一个普通的位置参数,而 `details` 就会接收所有额外的关键字参数。当我们调用 `display_info("张三", age=25, city="北京", job="工程师")` 时,`"张三"` 被赋给了 `name`,而 `age=25, city="北京", job="工程师"` 这几个关键字参数就组成了一个字典 `{'age': 25, 'city': '北京', 'job': '工程师'}`,赋值给了 `details`。
`kwargs` 的位置也很有讲究
`kwargs` 必须放在所有普通位置参数和 `args` 之后。
```python
def another_example(a, args, b, kwargs):
print(f"a: {a}")
print(f"args: {args}")
print(f"b: {b}")
print(f"kwargs: {kwargs}")
调用演示
another_example(1, 2, 3, b=4, city="上海", hobby="阅读")
输出:
a: 1
args: (2, 3)
b: 4
kwargs: {'city': '上海', 'hobby': '阅读'}
注意:b 必须是关键字参数传进来,不能是位置参数,除非是在 args 之前
another_example(1, 2, 3, 4, city="上海") 这样会报错,因为 4 无法确定是给 args 还是 b
```
应用场景:
处理不确定数量的关键字参数: 比如配置选项,或者一些框架的灵活接口。
函数装饰器: 装饰器包裹函数时,经常需要传递被装饰函数可能有的所有关键字参数。
函数调用时的“解包”: `` 同样可以用在函数调用时,把一个字典的键值对“解包”成独立的关键字参数传递进去。
```python
user_profile = {"age": 30, "occupation": "开发者", "email": "test@example.com"}
display_info("赵六", user_profile)
输出:
姓名: 赵六
额外信息:
age: 30
occupation: 开发者
email: test@example.com
```
这里的 `user_profile` 把 `user_profile` 字典里的 `age=30`, `occupation="开发者"`, `email="test@example.com"` “拆开”,变成了 `display_info("赵六", age=30, occupation="开发者", email="test@example.com")`。
`` 和 `` 组合使用:极大的灵活性
你也可以同时使用 `args` 和 `kwargs`,这使得函数可以接收任意数量的位置参数和任意数量的关键字参数。
```python
def powerful_function(first_arg, variadic_args, variadic_kwargs):
print(f"第一个固定参数: {first_arg}")
print(f"其他位置参数: {variadic_args}")
print(f"关键字参数: {variadic_kwargs}")
powerful_function(100, 200, 300, name="Alice", city="London")
输出:
第一个固定参数: 100
其他位置参数: (200, 300)
关键字参数: {'name': 'Alice', 'city': 'London'}
```
总结一下:
`args`: 在函数定义时,用于收集任意数量的位置参数,并将它们放入一个元组。在函数调用时,用于将一个可迭代对象(如列表、元组)的元素解包成多个位置参数。
`kwargs`: 在函数定义时,用于收集任意数量的关键字参数,并将它们放入一个字典。在函数调用时,用于将一个字典的键值对解包成多个关键字参数。
掌握了 `args` 和 `kwargs`,你就掌握了 Python 函数参数传递的“瑞士军刀”,能写出更加灵活、通用、健壮的代码。它们是 Python 动态性和表达力的一个绝佳体现。