*args将额外的位置参数收集到 元组 中。**kwargs将额外的关键字参数收集到 字典 中。- 这些名称只是一种约定,因为
*和**才是最重要的。 - 当您调用函数时,同样会解包列表或字典。
迟早,您会阅读像 def handler(*args, **kwargs) 这样的函数并想知道它接受什么。 Python *args 和 **kwargs 让函数接受灵活数量的参数。它们为装饰器、包装器和许多库 API 提供支持。
本指南介绍了它们如何在两个方向上工作,以及 Python 让您控制函数调用方式的新方法。我们在 Python 3.13 上运行每个示例,显示的输出来自该运行。
*args 的作用是什么
用一颗星编写的参数将所有额外的位置参数收集到一个元组中:
def total(*args):
print(type(args).__name__, args)
return sum(args)
print(total(1, 2, 3))
# tuple (1, 2, 3)
# 6
print(total())
# tuple ()
# 0
如果没有参数,您只需得到一个空元组,因此该函数仍然有效。名称 args 是一种约定。 *numbers 的行为是相同的,并且更具描述性的名称通常更好。
**kwargs 做什么
两颗星将所有额外的关键字参数收集到字典中:
def describe(**kwargs):
print(type(kwargs).__name__, kwargs)
describe(name="Asha", city="Pune")
# dict {'name': 'Asha', 'city': 'Pune'}
因为它是一个普通的 字典 ,所以您可以循环它,使用 get() 查找键并将其传递。密钥保持其传递的顺序。
参数顺序
当混合各种参数时,Python 需要特定的顺序:
- 常规位置参数
*参数- 仅限关键字参数
**夸格
def order(item, qty=1, *extras, priority=False, **notes):
print(item, qty, extras, priority, notes)
order("pen")
# pen 1 () False {}
order("pen", 2, "gift wrap", "card", priority=True, note="leave at door")
# pen 2 ('gift wrap', 'card') True {'note': 'leave at door'}
注意priority。因为它位于 *extras 之后,所以只能通过名称设置,因此杂散的位置参数永远不会意外设置它。不允许将 **kwargs 放在前面,我们的测试提出了 SyntaxError:“参数不能跟随 var-keyword 参数”。
打电话时拆箱
相同的星星在呼叫站点以相反的方式工作。一颗星将列表或元组扩展为位置参数,两颗星将字典扩展为关键字参数:
nums = [4, 5, 6]
total(*nums) # same as total(4, 5, 6) -> 15
settings = {"name": "Ben", "city": "Delhi"}
describe(**settings) # same as describe(name="Ben", city="Delhi")
解包适用于任何函数,而不仅仅是那些使用 *args 和 **kwargs 的函数。当配置以字典形式出现时,这很方便:
def connect(host, port, timeout=5):
return f"{host}:{port} (timeout {timeout})"
connect(**{"host": "db.local", "port": 5432})
# 'db.local:5432 (timeout 5)'
connect(**{"host": "db.local", "port": 5432, "ssl": True})
# TypeError: connect() got an unexpected keyword argument 'ssl'
这个错误很有用。没有 **kwargs 的函数会拒绝未知选项,因此设置名称中的拼写错误会大声失败,而不是被默默地忽略。
仅关键字参数
签名中的裸 * 没有名称,表示“此后的所有内容都必须通过关键字传递”:
def make_user(name, *, admin=False):
return name, admin
make_user("Asha", admin=True)
# ('Asha', True)
make_user("Asha", True)
# TypeError: make_user() takes 1 positional argument but 2 were given
这是保护布尔标志的好方法。像 make_user("Asha", True) 这样的调用迫使读者猜测 True 的含义。要求 admin=True 使每个调用都不言自明。
仅位置参数
从Python 3.8开始,a /标记只能按位置传递的参数:
def area(width, height, /):
return width * height
area(3, 4) # 12
area(width=3, height=4)
# TypeError: area() got some positional-only arguments
# passed as keyword arguments: 'width, height'
这使得库可以在以后重命名这些参数而不会破坏任何人的代码,因为没有人可以依赖这些名称。一些内置函数就是这样工作的。例如,len(obj=[1]) 不起作用。
提示:完整模式为def f(pos_only, /, normal, *, kw_only)。您很少需要全部三个,但了解 / 和 * 的含义可以帮助您阅读文档和库签名。
装饰器和包装器
这是同时使用两者的主要原因。包装器通常需要接受原始函数采用的任何参数,并将它们原封不动地传递:
import functools, time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__} took {time.perf_counter() - start:.4f}s")
return result
return wrapper
@timed
def slow_add(a, b, delay=0.01):
time.sleep(delay)
return a + b
print(slow_add(2, 3, delay=0.02))
# slow_add took 0.0205s
# 5
包装器根本不需要知道 slow_add 的参数,因此它适用于任何函数。您的时间数字会略有不同。我们的 Python 装饰器 指南解释了 functools.wraps 和其他细节。
合并选项
** 也适用于字典文字,它提供了一种在默认值之上应用覆盖的巧妙方法:
defaults = {"theme": "light", "lang": "en"}
def build(**overrides):
return {**defaults, **overrides}
build(theme="dark")
# {'theme': 'dark', 'lang': 'en'}
后来的键获胜,因此覆盖替换默认值。子类也使用此模式(如 super().__init__(*args, **kwargs) 中所示)将额外的参数传递给父类。有关 super() 的更多信息,请参阅 Python 类和对象 。
备忘单
| 语法 | 哪里 | 含义 |
|---|---|---|
def f(*args) |
定义 | 将额外的位置参数收集到元组中 |
def f(**kwargs) |
定义 | 将额外的关键字参数收集到字典中 |
def f(a, *, b) |
定义 | b 仅限关键字 |
def f(a, /, b) |
定义 | a 仅限位置 |
f(*items) |
致电 | 将序列分散到位置参数中 |
f(**options) |
致电 | 将字典传播到关键字参数中 |
{**a,**b} |
字面意思 | 合并字典 |
[*a,*b] |
字面意思 | 将可迭代对象合并到一个列表中 |
何时避免它们
灵活性是有代价的。定义为 def create(**kwargs) 的函数不会告诉读者存在哪些选项,并且编辑器无法自动完成它们。在任何失败之前,打字错误可能会悄悄地通过几层。
因此,当您知道函数需要什么时,更喜欢显式参数。当您真正需要灵活性时,请使用 *args 和 **kwargs:包装器、装饰器、将参数转发到另一个函数,或像 total(*numbers) 这样的真正的可变长度输入。如果一个函数需要很多命名选项,dataclass也可以更清晰。
常见错误
使用可变默认值
这与星星无关,但它经常出现在星星附近。默认值在定义函数时创建一次,然后在调用之间共享:
def bad(items=[]):
items.append(1)
return items
print(bad(), bad())
# [1, 1] [1, 1]
两次调用都返回了完全相同的列表,其中包含两个项目。使用 items=None 并在函数内创建一个新列表。
其他错误
- 忘记解压。调用
total(nums)传递一个列表而不是三个数字。写total(*nums)。 - 吞掉拼写错误。 接受
**kwargs但忽略未知键的函数会隐藏拼写错误的选项。验证它们,或者避免**kwargs。 - 两次传递相同的参数。
f(1, **{"a": 2})(其中a是第一个参数)会引发“多值”错误。 - 不在包装器中转发。 调用
func(args, kwargs)传递一个元组和一个字典。正确的形式是func(*args, **kwargs)。
*args 和 **kwargs 的类型提示
*args 和 **kwargs 上的类型提示描述每个单独的值,而不是整个集合。这让很多人感到惊讶:
def total(*prices: float) -> float:
return sum(prices) # prices is tuple[float, ...]
def tag(**attrs: str) -> str:
return " ".join(f'{k}="{v}"' for k, v in attrs.items()) # dict[str, str]
total(1.5, 2.5) # 4.0
tag(id="x", role="button") # id="x" role="button"
这里 prices 被视为浮点数元组,attrs 被视为字符串字典。当关键字参数具有不同类型时,Python 3.12 添加了一个精确选项:TypedDict 与 Unpack 组合。
from typing import TypedDict, Unpack
class Options(TypedDict, total=False):
timeout: float
retries: int
def fetch(url: str, **opts: Unpack[Options]) -> None: ...
然后,诸如 mypy 或 Pyright 之类的类型检查器可以拒绝 fetch(url, retries="3") 或拼写错误的选项名称,而普通的 **kwargs 永远无法做到这一点。但运行时行为不会改变。当我们在 Python 3.13 下运行 fetch("u", retries="3") 时,它执行时没有任何抱怨,因为 Python 本身不强制执行类型提示。
如果您发现自己记录了 **kwargs 的一长串可接受的键,请考虑用显式的仅关键字参数替换它们。然后编辑可以自动完成它们,并且错误会立即被发现。
最后的话
记住两个方向。在定义中,星星聚集。在通话中,他们打开行李。添加仅关键字参数以使调用更清晰,并为真正转发参数的代码保留 **kwargs。为了练习,编写一个小的装饰器来记录每个调用的参数,并将其用于几个不同的函数。
常见问题
*args 和 **kwargs 有什么区别?
*args 将额外的位置参数收集到一个元组中。 **kwargs 将额外的关键字参数收集到字典中。一个函数可以同时使用两者。
我必须将它们称为 args 和 kwargs 吗?
不会。星星负责工作,因此 * 值或 ** 选项同样有效。 args 和 kwargs 只是一个广泛认可的约定。
*args 和 **kwargs 应该按什么顺序排列?
首先是常规参数,然后是 *args,然后是仅关键字参数,最后是 **kwargs。任何其他顺序都会导致语法错误。
函数定义中单个 * 意味着什么?
裸露的 * 表示后面的每个参数都必须通过关键字传递。它使调用更加清晰,特别是对于布尔标志。
Python 函数中 / 是什么意思?
/ 之前的参数仅是位置参数,因此调用者无法通过名称传递它们。该功能是在 Python 3.8 中添加的。
继续学习
需要测试、WordPress 或 SEO 方面的帮助?
我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。
更多指南 测试、人工智能和现代开发
喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。
人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它
人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。
阅读文章 →DevOps环境变量解释:.env 文件、秘密和配置正确完成
使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。
阅读文章 →软件开发API 速率限制解释:算法、标头和处理 429 错误
API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。
阅读文章 →测试自动化Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误
使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。
阅读文章 →软件测试代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义
用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。
阅读文章 →数据库与 SQLSQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们
DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。
阅读文章 →数据库与 SQLSQL 视图解释:创建、使用和避免陷阱(附示例)
SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。
阅读文章 →软件开发TypeScript 接口与类型:真正的差异,使用编译器进行测试
TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。
阅读文章 →软件开发Python *args 和 **kwargs 解释:灵活的函数和清晰的示例
Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。
阅读文章 →软件开发Python Lambda 函数:语法、实际用途以及何时不使用它们
Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。
阅读文章 →人工智能什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环
什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。
阅读文章 →DevOps监控与可观察性:真正的区别,包括日志、指标和跟踪
监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。
阅读文章 →DevOpsDocker Volumes 与 Bind Mounts:以正确的方式保留容器数据
Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。
阅读文章 →测试自动化TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式
TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。
阅读文章 →软件开发Git merge 与 rebase 的区别:示例及如何选择
用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。
阅读文章 →软件开发Python 列表推导式:语法、示例与何时避免使用
Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。
阅读文章 →数据库与 SQLSQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)
通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。
阅读文章 →