你真的了解 Python 中的`*args`吗?

原文:towardsdatascience.com/do-you-really-know-args-in-python-1e3402c77190

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/98f3c18474c86043eb529e1956d43cca.png

图片由Miguel Á. Padriñán来自Pixabay提供

作为 Python 中最独特的语法之一,*args将在编程过程中给我们带来很多灵活性和便利。我可以说,它们反映了“Pythonic”和 Python 之道的精髓。

然而,我发现它们对学习者来说很难理解。在这篇文章中,我将尽我所能解释这个标志性的 Python 概念,并基于我的知识提供实际用例。希望这能帮助你更好地理解它。

1. “*args”究竟是什么?

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/663f6a8af8a764481720e506ee73980b.png

图片由托马斯来自Pixabay提供

*args代表“参数”。它允许我们向函数传递任意数量的位置参数(稍后解释)到函数中。在函数内部,我们可以以元组的形式获取所有位置参数。因此,我们可以在函数中对参数的元组做任何操作。

这里是*args的一个简单示例。

def add_up(*numbers):
    result = 0
    for num in numbers:
        result += num
    return result

print(add_up(1, 2, 3))

当我们调用这个add_up()函数时,我们向它传递了三个位置参数。在 Python 中,如果我们没有指定参数的名称,它们将被视为位置参数。这些参数根据它们的顺序确定,因此它们被称为位置参数。

在上面的例子中,所有的位置参数1, 2, 3都被传递到函数中,并被*numbers参数“捕获”。然后,我们可以从这个参数numbers中访问所有这些参数。星号*简单地告诉 Python 这是一个*args类型的参数。之后,一个简单的 for 循环将所有参数相加并打印结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/6fec88f85ea47501a7983354916f6fb4.png

如上所述,*arg的美丽之处在于它可以接受任意数量的位置参数。因此,如果我们需要的话,我们可以传递更多的参数。

print(add_up(1, 2, 3, 4))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f22b72c9717b394c7de8337c9305971b.png

在这里,我们可以通过向原始函数添加一行代码来验证变量numbers是否是一个元组。

def add_up(*numbers):
    print(type(numbers))
    result = 0
    for num in numbers:
        result += num
    return result

print(add_up(1, 2, 3))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9701ef759967032e8e69887814d687de.png

Python 使用元组来包含这些参数的原因主要是由于其不可变性。因此,它们在创建后不能被修改。

2. *args的实际用例

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a44a0d72bd9660c69f214ffc967d1144.png

图片由 Rudy 和 Peter Skitterians 提供,来自 Pixabay

现在,让我们看看 *args 的实际用例。由于它允许传递任意数量的参数,在许多场景中,我们不知道需要传递多少参数给函数,这将使其成为最佳使用场景。

2.1 生成动态 SQL 查询

其中一个常见的用例是生成 SQL 查询。

假设我们需要编写一个函数来生成具有未知数量过滤条件的 SELECT 语句。在大多数其他编程语言中,有两个痛点。

  1. 我们需要构建一个收集类型变量,如数组,来打包所有条件。然后,我们需要在函数内部解包所有条件。

  2. 我们不知道条件数量的多少。它可能是零。我们还需要处理条件是否应该从 “WHERE” 或 “AND” 开始。一些开发者喜欢在查询中添加 “WHERE 1=1”,这样所有的条件都可以从 “AND” 开始。

这两个痛点都可以在 Python 中优雅地解决。看看下面的代码。

# Generating Dynamic SQL Queries
def create_query(table, *conditions):
    sql = f"SELECT * nFROM {table}"
    if conditions:
        return sql + "nWHERE " + "nAND ".join(conditions)
    return sql

*conditions 是一个 *args 参数,它接受零个或多个条件。函数首先构建 SELECT 查询,然后检查 conditions 中是否有任何参数。如果有,使用 .join() 函数构建条件子句。

让我们看看一些结果。从零条件开始。

# Without condition
print(create_query("Users"))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/40ddc25d6016fbfe59c0d6a2fc52d8b3.png

如果只有一个条件,"nAND ".join(conditions) 将给出元组中唯一的条件。由于它只是一个连接符,所以 “AND” 不会出现。

# With one condition
print(
    create_query("Users", 
                 "age > 18"
))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/a8ea43f54ef94f177480220565a1ba65.png

如果我们有多个条件,它也会工作。这里将 “AND” 用作两个条件字符串之间的连接符。

# With multiple conditions
print(
    create_query("Users", 
                 "age > 18", 
                 "status = 'active'",
                 "is_vip = 'true'"
))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/e09a5667e07ba25361ff8e5618de1de9.png

多么优雅的解决方案!

BTW,如果你想优雅地构建一些复杂的 SQL 查询,可能比玩字符串操作有更好的实践。查看这篇文章了解更多关于名为 sqlglot 的工具的信息。

ChatGPT 并非总是理解 SQL,但这个 Python 工具可以

此外,如果你不太熟悉 <str>.join() 函数,这里有一篇文章可以直观地解释它。

在 Python 中不要使用 “+” 来连接字符串

2.2 灵活的日志消息

假设我们正在开发需要为许多不同类型的消息记录日志的软件。痛点在于这些不同类型的消息有不同的组成部分。

例如,用户登录消息只需要告诉活动类型和谁登录了。另一方面,上传文件日志消息有更多组件,如文件名、大小和经过的时间。

当然,我们可以不使用*args来实现这一点。然而,我们可能需要在将所有组件传递给log_messages()函数之前构建一个列表,或者我们必须在将它们传递给函数之前将组件连接成一个单独的字符串。

使用*argslog_messages()函数并不关心有多少个组件。

from datetime import datetime

def log_messages(*msg):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    full_message = " | ".join(msg)
    print(f"[{timestamp}] {full_message}")

# Usage examples
log_messages("User logged in", "Username: Chris")
log_messages("File uploading", "Filename: report.pdf", "/ctao/document/report.pdf")
log_messages("File uploaded", "Filename: report.pdf", "Size: 2MB", "Elapsed Time: 1.3s")

在上述代码中,我们实现了这个log_messages()函数。它首先获取当前时间戳。然后,所有组件字符串通过分隔符连接起来以提高可读性。最后,打印日志。

使用示例仅用于演示目的。在实践中,这些更可能是变量。

结果看起来很棒。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/8c7005411b93cc605c62023a8c9c79dc.png

2.3 集合上的计算

有时候,我们需要在列表和集合等集合类型上执行一些计算。在这种情况下,如果我们想将几个列表放入一个列表中,这当然可以工作,但在可读性和灵活性方面可能不是最佳选择。

在这种情况下,*args会有所帮助。

def find_common_elements(*datasets):
    # Initialize the common elements set with the first dataset
    common_elements = datasets[0] if datasets else {}

    # Intersect with the remaining datasets
    for dataset in datasets[1:]:
        common_elements.intersection_update(dataset)

    return common_elements

# Usage examples:
dataset1 = {1, 2, 3, 4}
dataset2 = {2, 3, 4, 5}
dataset3 = {3, 4, 5, 6}

common_elements = find_common_elements(dataset1, dataset2, dataset3)
print(f"The common elements in the datasets are: {common_elements}")

在上述代码中,find_common_elements()函数接受任意数量的集合,并将它们的交集获取出来。它使用第一个集合来初始化公共集合。然后,使用公共集合与剩余的其他集合进行交集操作。结果如下。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9d1983ade35b83bfd086064e5f2b1abf.png

3. 一些 Python 原生对*args 的使用

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/664fa6aa53f3934d7c307db4b9e22117.png

图片由Markus WeberPixabay提供

作为最独特的语法之一,*args不出所料地被用在许多 Python 内置模块及其函数中。以下是一些示例。

让我们看看这些原生示例以及为什么在这些场景中使用*args。这些是非常好的参考,可以用来指导我们的编码。你不会找到比 Python 本身更好的 Python 教程了 🙂

3.1 路径连接

我首先想到的是os.path.join()方法。在我们处理文件系统时,这是最常用的函数之一。

例如,如果我们想将这些组件连接起来并构建一个文件路径,os.path.join()将是最简单的方法。

import os

# Take any number of path components
path = os.path.join("Users", "CTao", "Documents", "Work", "report.txt")

print(path)

这里是结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/92289584c830a0298c62a485d56b509a.png

在这个函数中,利用 *args 给我们提供了最大的灵活性,因为我们不需要担心路径组件的数量。这也提高了代码的可读性,因为我们不需要将这些组件放入任何集合类型变量中。

3.2 最小值和最大值

我想到的最简单的例子就是 min()max() 函数。

print(max(1, 2, 3, 4, 5))
print(min(1, 2, 3, 4, 5))

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d69d3947802828f900ca7b7b9b3fe2c2.png

它们只是分别从 *args 中获取最大和最小数字,我们不需要向它传递列表。

BTW,你可能发现 max()min() 实际上也支持可迭代参数。请看以下示例。

max([1, 2, 3, 4, 5])

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/105da62264d70c21be9f5036654137da.png

多么灵活啊!它考虑了两种场景,并支持两种直观的使用此函数的方式。如果你想在代码中做类似的事情,一个简单的方法是检查 args[0]。如果是列表,就取列表。否则,使用整个 args 并迭代它。

3.3 打印函数

我相信你们中的大多数人都会知道 print() 函数也支持 *args 模式。当我们使用这种模式时,字符串的默认分隔符将是一个空格。

print("Towards", "Data", "Science")
print("Towards", "Data", "Science", sep=" | ")

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d71d2246b2c84869839c0b11d4a26957.png

在第二个例子中,sep=" | " 帮助我们自定义分隔符。这表明我们可以将 *arg 与其他关键字参数一起使用。

4. 不要将 *args 用于所有情况!

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/2163eb42bf973fe36a1b661dc0c52179.png

图片由 Free Photos 来自 Pixabay 提供

当然,我写这篇文章是为了鼓励你们使用 *args。然而,请不要误解我。我并不是说你们应该一直使用它。现在,让我给你展示一个使用 *args 的伪代码示例,这是一个不好的例子。

def my_function(*args):
    result = args[1] + args[2]
    if result > 100:
        result += args[4]
    if args[5] != args[6]:
        return result + args[7]
    else:
        return result

在这个例子中,尽管总共使用了 8 个参数,但使用 *args 实际上损害了可读性。在这种情况下,函数严格依赖于参数的位置。使用命名参数或具有特定键的字典会更好,这将在理解函数的确切逻辑方面提高可读性。

摘要

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b23ced0e8b7944689b7d14c9e25fa17a.png

图片由 Leopictures 来自 Pixabay

在这篇文章中,我们深入探讨了 Python 中最具标志性的语法之一——*args。它在开发函数以及后续使用该函数的用户方面提供了极大的灵活性。我希望你现在对它有了更深的理解。

除了这些,我还提供了一些关于*args的实际和典型用法。肯定还有更多和更好的场景,但文章中的例子都来源于我的日常使用。请随意评论这篇文章,为后续读者提供更多优秀的例子。

最后,我还列出了一些利用*args技巧的 Python 原生示例。这些都是“官方”示例,非常有效地展示了实际的应用场景。

希望这篇文章能有所帮助。如果您想了解更多关于**kwargs的信息,请继续关注我,下一篇文章将会很快发布。

<think>首先,用户的问题是:“我想了解Python*args和**kw的相关知识 请问Python*args和**kw的用法和原理”。系统指令要求我使用中文回答,并遵循特定的格式:行内数学表达式用$...$,独立公式用$$...$$单独成段,确保LaTeX语法正确。生成相关问题,并在回答中引用的段落末尾添加引用标识。 我有三个引用: - 引用[1]:解释*args和**kwargs的基本概念。 - 引用[2]:讨论它们的使用方法,包括位置和顺序,以及装饰器中的应用。 - 引用[3]:展示了一个代码示例,使用**kwargs构建字典。 用户的问题是关于*args和**kw的用法和原理。我需要基于这些引用构建一个结构清晰的回答。系统指令还要求生成相关问题。 回答结构: - 引言:简要介绍*args和**kwargs。 - 详细解释用法:包括位置参数、关键字参数、顺序规则等。 - 原理:解释它们的内部工作原理(如解包、可变参数)。 - 示例代码:展示用法。 - 总结:强调优点和注意事项。 - 生成相关问题:结尾添加相关问题。 确保回答真实可靠,基于提供的引用。引用末尾添加引用标识如[^1]。 关键点从引用中提取: - *args用于位置参数,**kwargs用于关键字参数。[引用1] - *args必须出现在**kwargs之前。[引用2] - 在函数定义和调用中可以同时使用。[引用2] - 在装饰器中特别有用。[引用2] - **kwargs可以转换为字典。[引用3] 我的回答应覆盖这些方面。 添加数学表达式:虽然问题不涉及数学,但系统指令要求所有行内数学表达式如变量用$...$。如果无关,我可以避免或只在必要时添加。例如,提到变量名时可以如$args$,但可能不必要。最好只在描述算法等时使用。 生成相关问题:结尾添加3-4个相关问题,类似于示例。 完整回答结构: 1. **介绍**:定义*args和**kwargs。 2. **用法**: - 在函数定义中:如何声明。 - 在函数调用中:如何使用解包。 - 顺序规则。 3. **原理**:解释Python如何处理可变参数。 4. **示例**:代码演示。 5. **注意事项**:引用中的警告。 6. **结论**:总结优势。 7. **相关问题**:生成相关问题。 使用引用: - 当引用具体点时,添加引用标识,如提到位置参数时[^1]。 相关问题思路: 1. 如何在实际项目中高效使用*args和**kwargs? 2. *args和**kwargs在装饰器中的具体应用例子有哪些? 3. 为什么位置参数必须在关键字参数之前? 4. 有没有替代*args和**kwargs的方法来处理可变参数? 现在,用中文撰写回答。 </think>### Python*args和**kwargs的用法与原理 在Python中,`*args``**kwargs`是处理函数参数灵活性的魔法变量,允许函数接受任意数量的位置参数和关键字参数。它们基于Python的参数解包机制,增强了代码的通用性和可扩展性。下面我将逐步解释其用法、原理,并结合示例说明。 #### 1. **基本用法** - **`*args`(位置参数)**:用于捕获任意数量的位置参数(即按顺序传递的参数)。在函数定义中,它将所有额外位置参数打包为一个元组(tuple)。例如,定义一个函数时: ```python def example_function(a, b, *args): print(f"固定参数: {a}, {b}") print(f"额外位置参数: {args}") example_function(1, 2, 3, 4) # 输出: 固定参数: 1, 2; 额外位置参数: (3, 4) ``` 这里,`*args`捕获了所有未被命名参数匹配的参数,形成一个元组[^1]。 - **`**kwargs`(关键字参数)**:用于捕获任意数量的关键字参数(即以键值对形式传递的参数)。它将额外关键字参数打包为一个字典(dict)。例如: ```python def example_function(a, b, **kwargs): print(f"固定参数: {a}, {b}") print(f"额外关键字参数: {kwargs}") example_function(1, b=2, c=3, d=4) # 输出: 固定参数: 1, 2; 额外关键字参数: {'c': 3, 'd': 4} ``` 在函数调用中,`**kwargs`会自动将键值对转换为字典[^3]。 - **顺序规则**:当同时使用时,`*args`必须位于`**kwargs`之前,因为位置参数必须在关键字参数之前定义。违反此规则会导致语法错误[^2]。 #### 2. **在函数调用中的解包原理** - **解包操作**`*``**`符号不仅在函数定义中使用,还可在函数调用中将序列或字典解包为参数。这基于Python的迭代和解包机制: - `*`解包序列(如列表或元组)为位置参数。 - `**`解包字典为关键字参数。 示例: ```python def example_function(a, b, c): return a + b + c params = (1, 2, 3) print(example_function(*params)) # 输出: 6 kwargs_dict = {'a': 1, 'b': 2, 'c': 3} print(example_function(**kwargs_dict)) # 输出: 6 ``` 解包原理依赖于Python的动态类型系统:解释器在运行时检查参数类型并将其展开。例如,`*args`本质是将任意位置参数转换为元组对象,内存中存储为一个引用序列[^1]。 #### 3. **核心原理** - **可变参数处理**Python函数参数模型允许静态参数(固定数量)和动态参数(变量数量)。`*args``**kwargs`使用解包操作符实现动态性: - `*args`:内部创建元组对象,存储所有额外位置参数(参数数量可变,类型为`tuple`)。 - `**kwargs`:内部创建字典对象,存储键值对(参数数量可变,类型为`dict`)。 这避免了硬编码参数数量,提高了函数复用性。 - **为什么使用解包符**`*``**`Python语法糖,简化了参数传递。例如,在数学函数中,解包允许处理多个变量如$f(x_1, x_2, \dots, x_n)$,其中$n$可变[^2]。 #### 4. **实用示例** 以下代码展示`*args``**kwargs`的组合应用: ```python # 组合使用:计算任意参数的乘积 def multiply(*args, **kwargs): result = 1 for num in args: # args是元组,迭代位置参数 result *= num for key, value in kwargs.items(): # kwargs是字典,迭代关键字参数 print(f"{key}: {value}") return result # 调用函数 print(multiply(2, 3, 4, scale=10)) # 输出: 24 (位置参数计算) 和 scale: 10 (关键字参数打印) ``` 在装饰器中,这种灵活性特别有用: ```python # 装饰器示例:记录函数参数 def log_args(func): def wrapper(*args, **kwargs): print(f"调用函数: {func.__name__}, 参数: {args}, {kwargs}") return func(*args, **kwargs) return wrapper @log_args def greet(name, message): print(f"{message}, {name}!") greet("Alice", message="Hello") # 输出: 调用函数: greet, 参数: ('Alice',), {'message': 'Hello'} 然后打印 Hello, Alice! ``` 此例中,`*args``**kwargs`捕获并传递任意参数给被装饰函数[^2]。 #### 5. **注意事项** - **优势**:提高函数通用性,例如在数据科学中处理可变输入如$a+b+c+\dots$。 - **缺点**:过度使用可能导致代码可读性下降,因为函数签名变得隐晦。建议仅在参数数量不确定的场景使用[^2]。 - **替代方案**:对于固定参数,优先使用命名参数;可变参数处理也可用列表或字典显式传递。 #### 总结 `*args``**kwargs`Python的核心特性,通过解包机制实现参数灵活性。`*args`处理位置参数(打包为元组),`**kwargs`处理关键字参数(打包为字典),顺序上必须`args`先于`kwargs`。它们在动态函数设计、装饰器中尤为重要,但需谨慎避免滥用[^1][^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值