0

0

*args 和 **kwargs 的作用与区别

紅蓮之龍

紅蓮之龍

发布时间:2025-09-05 20:16:02

|

710人浏览过

|

来源于php中文网

原创

答案:args和kwargs提供灵活参数处理,args收集位置参数为元组,kwargs收集关键字参数为字典,适用于通用函数、装饰器、参数解包等场景,提升代码灵活性。

*args 和 **kwargs 的作用与区别

*args
**kwargs
是 Python 中处理函数可变参数的两个核心机制。简单来说,
*args
允许你向函数传递任意数量的位置参数,它们会被收集成一个元组;而
**kwargs
则允许你传递任意数量的关键字参数,它们会被收集成一个字典。这俩玩意儿让函数的灵活性直接上了一个台阶,你不需要提前知道调用者会给你多少个参数。

解决方案

在我看来,Python 的

*args
**kwargs
简直是函数设计者的“瑞士军刀”,尤其是在需要构建通用或高度灵活的函数时。它们的核心作用就是提供了一种优雅的方式来处理那些在函数定义时,你无法确定具体数量的参数。

*`args` 的妙用**

当你定义一个函数,但又不确定调用者会传入多少个非关键字参数时,

*args
就派上用场了。它会把所有传入的、未被显式匹配的位置参数“打包”成一个元组。

比如说,你想写一个函数,可以计算任意多个数字的和:

def calculate_sum(*numbers):
    total = 0
    for num in numbers:
        total += num
    return total

print(calculate_sum(1, 2, 3))         # 输出: 6
print(calculate_sum(10, 20, 30, 40))  # 输出: 100
print(calculate_sum())                # 输出: 0

你看,

*numbers
在这里就扮演了一个收集器的角色。调用的时候,你可以传三个数,也可以传四个,甚至不传,函数都能正常工作。我个人觉得,
*args
最妙的地方在于它提供了一种优雅的方式,来处理那些“我不知道你会给我多少个”的参数场景,特别适合像日志记录、数据聚合这类操作。

`kwargs` 的魔法**

**kwargs
呢,它处理的是关键字参数。当你的函数可能需要接收一些可选的、具名的配置参数,但你又不希望把所有可能的参数都列在函数签名里时,
**kwargs
就是你的救星。它会将所有未被显式匹配的关键字参数“打包”成一个字典。

想象一下,你正在写一个配置生成器函数:

def create_config(name, version="1.0", **options):
    config = {
        "name": name,
        "version": version
    }
    config.update(options) # 将所有额外选项添加到配置中
    return config

print(create_config("MyApp", debug_mode=True, log_level="INFO"))
# 输出: {'name': 'MyApp', 'version': '1.0', 'debug_mode': True, 'log_level': 'INFO'}

print(create_config("AnotherApp", version="2.0", timeout=300))
# 输出: {'name': 'AnotherApp', 'version': '2.0', 'timeout': 300}

这里,

**options
收集了
debug_mode
,
log_level
,
timeout
这些关键字参数,并将它们放进了一个字典里。
**kwargs
就像是给你的函数开了一个“VIP通道”,你爱传什么配置就传什么,我都能收着,然后统一处理。这种模式在构建像 API 客户端、ORM 查询器这类需要高度可配置性的工具时,简直是神器。

两者结合与参数解包

当然,

*args
**kwargs
可以同时使用。但要记住一个顺序:*普通参数 -youjiankuohaophpcn `args
->
kwargs`

def process_data(prefix, *items, suffix="END", **metadata):
    print(f"Prefix: {prefix}")
    print(f"Items: {items}")
    print(f"Suffix: {suffix}")
    print(f"Metadata: {metadata}")

process_data("START", 1, 2, 3, key1="value1", key2="value2")
# Prefix: START
# Items: (1, 2, 3)
# Suffix: END
# Metadata: {'key1': 'value1', 'key2': 'value2'}

除了收集参数,它们还有另一个同样重要的功能:参数解包 (Unpacking)。你可以用

*
来解包一个可迭代对象(如列表或元组),将其元素作为独立的位置参数传递;用
**
来解包一个字典,将其键值对作为独立的关键字参数传递。

my_list = [10, 20, 30]
my_dict = {"city": "New York", "country": "USA"}

def display_info(a, b, c, city, country):
    print(f"Numbers: {a}, {b}, {c}")
    print(f"Location: {city}, {country}")

# 解包列表和字典
display_info(*my_list, **my_dict)
# Numbers: 10, 20, 30
# Location: New York, USA

这个解包操作,我觉得是真正把灵活性推到极致的地方。它让函数调用者能够以一种非常动态的方式来组织和传递参数,无论是从一个列表,还是从一个配置字典里。

在哪些实际场景中,
*args
**kwargs
能发挥最大价值?

当我们在写 Python 代码时,总会遇到一些需要函数具备高度适应性的情况。在我看来,

*args
**kwargs
在以下几个场景中,简直是不可或缺的利器:

1. 构建通用型工具函数

设想你需要一个日志记录函数,它可能需要记录一条简单的消息,也可能需要记录多条消息,甚至带上一些额外的上下文信息。如果每次都为不同数量的参数定义不同的函数,那代码会变得非常臃肿。

import datetime

def log_message(level, *messages, **context):
    timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] [{level.upper()}] "

    # 收集并连接所有消息
    log_entry += " ".join(str(msg) for msg in messages)

    # 添加上下文信息
    if context:
        log_entry += " | " + ", ".join(f"{k}={v}" for k, v in context.items())

    print(log_entry)

log_message("INFO", "User logged in.", user_id=123, ip_address="192.168.1.1")
log_message("WARNING", "Database connection lost!")
log_message("DEBUG", "Processing item", 42, "with status", "SUCCESS")

你看,这个

log_message
函数通过
*messages
**context
,可以灵活地处理不同数量的消息和任意的上下文信息,而不需要为每种日志类型都写一个独立的函数。

2. 装饰器 (Decorators) 的基石

装饰器是 Python 中一个非常强大的元编程特性,它允许你在不修改原函数代码的情况下,给函数添加额外的功能。而要实现一个通用的装饰器,能够包装任何参数签名的函数,

*args
**kwargs
是必不可少的。

import time

def timer_decorator(func):
    def wrapper(*args, **kwargs): # 使用 *args, **kwargs 捕获被装饰函数的所有参数
        start_time = time.time()
        result = func(*args, **kwargs) # 将捕获的参数原封不动地传给原函数
        end_time = time.time()
        print(f"'{func.__name__}' executed in {end_time - start_time:.4f} seconds.")
        return result
    return wrapper

@timer_decorator
def complex_calculation(a, b, c):
    time.sleep(0.5)
    return a + b + c

@timer_decorator
def simple_greeting(name):
    time.sleep(0.1)
    return f"Hello, {name}!"

complex_calculation(1, 2, 3)
simple_greeting("Alice")

这里,

wrapper
函数必须能够接受任何
func
可能接收的参数,所以
*args
**kwargs
是唯一的选择。它们确保了装饰器可以通用地应用于各种函数。

3. 构建代理或转发函数

有时,你可能需要创建一个函数,它本身不执行太多逻辑,而是把所有接收到的参数原封不动地传递给另一个函数或方法。这在构建 API 封装、ORM 查询接口或者简单地重构代码时非常有用。

import requests

class APIClient:
    def __init__(self, base_url):
        self.base_url = base_url

    def get(self, endpoint, **kwargs): # 收集所有关键字参数
        url = f"{self.base_url}/{endpoint}"
        print(f"Making GET request to: {url} with params: {kwargs}")
        response = requests.get(url, **kwargs) # 将参数转发给 requests.get
        response.raise_for_status()
        return response.json()

client = APIClient("https://api.example.com")
# 假设 api.example.com/users?id=123&name=Alice 有数据
# client.get("users", params={"id": 123, "name": "Alice"})
# client.get("products", headers={"Authorization": "Bearer token"}, timeout=5)

在这个

APIClient
get
方法中,
**kwargs
收集了所有额外的参数(比如
params
,
headers
,
timeout
等),然后直接解包传递给
requests.get
。这使得
APIClient
能够非常灵活地代理
requests
库的功能。

使用
*args
**kwargs
时有哪些常见的误区或需要注意的地方?

虽然

*args
**kwargs
提供了巨大的灵活性,但如果使用不当,也可能引入一些问题。作为开发者,我们得清楚这些“坑”在哪里,才能更好地驾驭它们。

DeepSider
DeepSider

浏览器AI侧边栏对话插件,集成多个AI大模型

下载

1. 可读性与可维护性下降

这是我个人觉得最需要警惕的一点。当一个函数签名里只有

(*args, **kwargs)
时,调用者很难一眼看出这个函数到底期望接收哪些参数,或者哪些参数是必须的,哪些是可选的。这就像一个黑箱,虽然灵活,但给使用者带来了困惑。

def mysterious_function(*args, **kwargs):
    # ... 里面可能处理各种参数,但外部无法得知
    pass

# 调用时,我不知道该传什么!
mysterious_function(1, 2, name="Alice", age=30)

建议: 尽量只在真正需要高度灵活性的场景下使用它们。对于那些有明确、固定参数的函数,还是老老实实地列出参数名,这样代码的可读性会好很多。如果必须使用,可以考虑在文档字符串(docstring)中详细说明

*args
**kwargs
预期接收的参数类型和作用。

2. 参数顺序问题

在函数定义中,参数的顺序是有严格要求的:*普通位置参数 -> 默认值参数 -> `args

-> 关键字参数 ->
kwargs`。如果顺序错了,Python 解释器会报错。

# 错误示例:*args 放在了普通参数之前
# def func(*args, a, b):
#     pass

# 错误示例:**kwargs 放在了 *args 之前
# def func(a, **kwargs, *args):
#     pass

# 正确顺序
def func(a, b=1, *args, c, **kwargs): # 注意这里的 c 是一个强制关键字参数
    print(f"a={a}, b={b}, args={args}, c={c}, kwargs={kwargs}")

func(10, 20, 30, 40, c=50, key1="value1")
# a=10, b=20, args=(30, 40), c=50, kwargs={'key1': 'value1'}

这个顺序问题,我刚开始学的时候也犯过几次错,被 Python 解释器无情地教育了。记住这个“口诀”就能避免大部分问题。

3. 参数解包时的类型不匹配

使用

*
**
进行参数解包时,要确保你解包的数据类型是正确的。
*
只能解包可迭代对象(如列表、元组、字符串),将其元素作为位置参数;
**
只能解包字典,将其键值对作为关键字参数。

def my_printer(a, b, c):
    print(a, b, c)

data_list = [1, 2, 3]
data_dict = {"a": 1, "b": 2, "c": 3}

my_printer(*data_list) # 正确
# my_printer(**data_list) # 错误:列表不能用 ** 解包

my_printer(**data_dict) # 正确
# my_printer(*data_dict) # 正确,但解包的是字典的键,而不是值

这种类型不匹配的错误,通常会在运行时暴露出来,导致

TypeError
。所以,在解包前最好确认一下数据结构。

4. 命名约定

虽然你可以给它们起任何名字,比如

*args_list
**config_dict
,但 Python 社区约定俗成的做法是使用
*args
**kwargs
。遵循这个约定能极大地提高代码的可读性和团队协作效率。我个人觉得,这种约定比强制规定更有效,因为它建立了一种无声的共识。

5. 性能考量(通常可忽略)

理论上,收集和解包参数会引入微小的运行时开销,因为 Python 需要创建元组或字典来存储这些参数。但在绝大多数实际应用中,这种开销是微不足道的,几乎可以忽略不计。我个人从没因为

*args
**kwargs
的性能问题而重构过代码。除非你正在编写一个对毫秒级性能有极致要求的底层库,否则不必为此过度担忧。

除了函数定义和调用,
*args
**kwargs
在 Python 生态中还有哪些进阶应用?

*args
**kwargs
的影响力远不止于普通的函数定义和调用。它们渗透在 Python 的许多高级特性和设计模式中,是构建灵活、可扩展代码的关键。

*1. 继承中的参数传递:`super().init(args, kwargs)`

面向对象编程中,当子类重写父类的

__init__
方法时,通常需要调用父类的
__init__
来初始化父类的部分。这时,
*args
**kwargs
就显得尤为重要,它们确保了子类能够无缝地将所有构造参数传递给父类,而无需子类知道父类具体需要哪些参数。

class Parent:
    def __init__(self, name, age, **kwargs):
        self.name = name
        self.age = age
        print(f"Parent initialized: {name}, {age}, extra: {kwargs}")

class Child(Parent):
    def __init__(self, name, age, hobby, *args, **kwargs):
        super().__init__(name, age, **kwargs) # 将 kwargs 传递给父类
        self.hobby = hobby
        print(f"Child initialized with hobby: {hobby}, args: {args}")

# Child 知道自己需要 name, age, hobby,但额外的参数可以交给 Parent 处理
c = Child("Alice", 10, "reading", favorite_color="blue", school="ABC")
# Parent initialized: Alice, 10, extra: {'favorite_color': 'blue', 'school': 'ABC'}
# Child initialized with hobby: reading, args: ()

这种模式非常常见,它让继承链上的类能够各自处理自己关心的参数,同时又保持了良好的协作性。

2.

functools.partial
的灵活组合

functools.partial
是一个非常酷的工具,它允许你“部分应用”一个函数,也就是固定函数的一些参数,创建一个新的函数。而新的函数仍然可以通过
*args
**kwargs
来接受剩余的参数。

from functools import partial

def greet(greeting_word, name, punctuation="!"):
    return f"{greeting_word}, {name}{punctuation}"

# 创建一个总是说 "Hello" 的函数
say_hello = partial(greet, "Hello")
print(say_hello("Bob"))             # 输出: Hello, Bob!
print(say_hello("Charlie", "???"))  # 输出: Hello, Charlie???

# 创建一个总是说 "Hi" 且带感叹号的函数
say_hi_exclam = partial(greet, "Hi", punctuation="!!!")
print(say_hi_exclam("David"))       # 输出: Hi, David!!!

partial
内部其实就是利用了
*args
**kwargs
来接收原始函数未被固定的参数,这使得它在创建高度定制化的回调函数或事件处理器时非常有用。

3. 元编程 (Metaclasses) 与类动态创建

在 Python 的元编程领域,

*args
**kwargs
更是不可或缺。当你需要动态地创建类,或者通过元类来修改类的行为时,它们被用来捕获传递给类构造函数(即
type()
或元类的
__new__
方法)的参数。

# 这是一个简化的例子,元编程通常更复杂
def create_my_class(name, bases, attrs, **kwargs):
    print(f"Creating class '{name}' with bases {bases}, attrs {attrs}, and extra: {kwargs}")
    # 可以在这里根据 kwargs 修改 attrs 或 bases
    return type(name, bases, attrs)

# 动态创建一个类,并传递额外的关键字参数
MyDynamicClass = create_my_class("MyDynamicClass", (object,), {"value": 10}, version="1.0", author="Me")

print(MyDynamicClass.value) # 输出: 10

虽然这个例子只是模拟了

type()
的行为,但它展示了
**kwargs
如何在类创建级别捕获并处理额外的元数据。在更复杂的元类中,
*args
**kwargs
允许元类接收并处理所有传递给类定义的参数,从而实现更强大的类定制能力。这部分内容确实有点深,但它揭示了
*args
**kwargs
在 Python 语言底层机制中的重要性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

338

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

225

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

138

2026.02.12

go语言 面向对象
go语言 面向对象

本专题整合了go语言面向对象相关内容,阅读专题下面的文章了解更多详细内容。

58

2025.09.05

java面向对象
java面向对象

本专题整合了java面向对象相关内容,阅读专题下面的文章了解更多详细内容。

65

2025.11.27

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

761

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1570

2023.10.24

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

69

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP课程
PHP课程

共137课时 | 13.6万人学习

JavaScript ES5基础线上课程教学
JavaScript ES5基础线上课程教学

共6课时 | 11.3万人学习

PHP新手语法线上课程教学
PHP新手语法线上课程教学

共13课时 | 1.0万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号