Python 深度探讨 *args

点击下方卡片,关注“小白玩转Python”公众号

作为Python中最独特的语法之一,*args 在编程过程中给我们带来了很多灵活性和便利性。我认为它们反映了“Pythonic”和“Python之禅”。然而,我发现它们对于学习者(尤其是初学者)来说很难理解。在本文中,我将尽力解释Python中这个标志性的概念,并基于我的知识提供实际用例。我希望这能帮助更好地理解它。

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

*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 循环将所有参数相加并打印结果。

94f8d6f70f489aa3c4beb544586f7907.jpeg

正如上面提到的,*args 的美妙之处在于它可以接受任意数量的位置参数。因此,如果需要,我们可以传递更多的参数。

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

25262d2419af5302dc2ba6e1325f621c.jpeg

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

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

415b0bcab3accb28bc6e338e6ad4e2a3.jpeg

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

2. *args 的实际用例

现在,让我们看看 *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() 函数构建条件子句。让我们看一些结果。从零条件开始。

# 没有条件
print(create_query("Users"))

a0ddcc90895dda63beea6363c05f74f3.jpeg

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

# 有一个条件
print(
create_query("Users",
"age > 18"
))

e2e1f01f148df1372a932512fa3cfda5.jpeg

如果有多个条件,也可以工作。在每两个条件字符串之间,将使用“AND”作为连接器。

# 有多个条件
print(
create_query("Users",
"age > 18",
"status = 'active'",
"is_vip = 'true'"
))

0aaa88659045f894ffe671e10b0661e7.jpeg

顺便说一句,如果您想要优雅地构建一些非常复杂的SQL查询,可能有比玩弄字符串更好的做法。查看这篇文章,了解有关名为 sqlglot 的工具的更多信息。

2.2 灵活的日志消息

假设我们正在开发一个需要记录各种不同类型消息的软件。问题在于,这些不同类型的消息具有不同的组件。例如,用户登录消息只需要告诉活动类型和用户登录了谁。另一方面,文件上传日志消息具有更多的组件,如文件名、大小和经过的时间。

当然,我们可以在没有 *args 的情况下实现这一点。但是,我们要么需要在将所有组件传递给 log_messages() 函数之前构建一个列表,要么在将它们传递给函数之前将组件连接在一起作为单个字符串。有了 *args,log_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() 函数。首先获取当前时间戳。然后,所有组件字符串都使用分隔符连接在一起,以提高可读性。最后,打印日志。示例用法仅供演示目的。在实践中,这些更可能是变量。结果看起来很棒:

5c074aad473bdb04e2a84d0fbe737127.jpeg


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() 函数接受任意数量的集合,并获取它们的交集。它使用第一个集合来初始化公共集合。然后,使用公共集合与其他集合进行交集运算。结果如下。

ed431cdd41898240cd97cccf260935af.jpeg


3. 一些 *args 的 Python 原生用法

作为最独特的语法之一,*args 不足为奇地在许多 Python 内置模块及其函数中使用。以下是一些示例。让我们看看这些原生示例以及为什么在这些场景中使用 *args。这些是非常好的参考,可以用来指导我们的编码。会不会得到比 Python 本身更好的 Python 教程。

3.1 路径拼接

我首先想到的是 os.path.join() 方法。当我们处理文件系统时,这是最常用的函数之一。例如,如果我们想将所有这些组件连接在一起并构建文件路径,os.path.join() 将是最简单的方法。

import os
# 接受任意数量的路径组件
path = os.path.join("Users", "CTao", "Documents", "Work", "report.txt")
print(path)

以下是结果:

bd7f62ea7593fdc55f3abb4d0a3a2288.jpeg

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

3.2 最大值和最小值

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

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

6c99a81b1ce7e9e28a1172d3a80e58f9.jpeg

它们分别从 *args 中获取最大值和最小值,我们不需要传递一个列表给它。顺便说一句,我们可能会发现 max() 和 min() 实际上也支持可迭代的参数。看下面的示例。

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

157b9c1a469527864ab2ec1495d06cc9.jpeg

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

3.3 打印函数

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

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

cc4bef109a8c92671cb6cb0916641ad2.jpeg

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

4. 不要为所有事情都使用 *args!

当然,我写这篇文章是为了鼓励使用 *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 损害了可读性。在这种情况下,函数严格依赖于参数的位置。最好使用命名参数或带有特定键的字典。这将在理解该函数的逻辑方面提高可读性。

总结

在本文中,我们深入探讨了 Python 中最具标志性的语法之一 — *args。它在开发函数和后续使用函数的用户方面提供了很大的灵活性。我希望现在我们对它有了更多的了解。

除此之外,我还提供了一些 *args 的实际和典型用法。可能会有更多更好的场景,但本文中的示例来自我的日常使用。欢迎评论本文,为下一位读者提供更多优秀的示例。

最后,我还列出了一些使用 *args 技巧的 Python 原生示例。这些是很好地展示了实际用例的“官方”示例。

·  END  ·

HAPPY LIFE

e2f08c13d38fdbf487c1a54782e0416b.png

本文仅供学习交流使用,如有侵权请联系作者删除

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值