点击下方卡片,关注“小白玩转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 循环将所有参数相加并打印结果。
正如上面提到的,*args 的美妙之处在于它可以接受任意数量的位置参数。因此,如果需要,我们可以传递更多的参数。
print(add_up(1, 2, 3, 4))
在这里,我们可以通过向原始函数添加一行来验证变量 numbers 是否是一个元组。
def add_up(*numbers):
print(type(numbers))
result = 0
for num in numbers:
result += num
return result
print(add_up(1, 2, 3))
Python使用元组来包含这些参数的原因主要是因为它们是不可变的。因此,在创建后它们不能被修改。
2. *args 的实际用例
现在,让我们看看 *args 的实际用例。由于它允许传递任意数量的参数,在许多情况下我们不知道需要向函数传递多少个参数,这将是它最好的使用场景。
2.1 生成动态SQL查询
其中一个常见用例是生成SQL查询。假设我们需要编写一个函数来生成带有未知数量过滤条件的SELECT语句。在大多数其他编程语言中有两个痛点。
我们需要构建一个集合类型的变量,比如数组,来打包所有的条件。然后,我们需要在函数内部解包所有的条件。
我们不知道条件的数量。它可能是零。我们还需要处理条件是否应该从“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"))
如果只有一个条件,“\nAND ”.join(conditions) 将是元组中唯一的条件。由于它只是一个连接器,所以“AND”不会出现。
# 有一个条件
print(
create_query("Users",
"age > 18"
))
如果有多个条件,也可以工作。在每两个条件字符串之间,将使用“AND”作为连接器。
# 有多个条件
print(
create_query("Users",
"age > 18",
"status = 'active'",
"is_vip = 'true'"
))
顺便说一句,如果您想要优雅地构建一些非常复杂的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() 函数。首先获取当前时间戳。然后,所有组件字符串都使用分隔符连接在一起,以提高可读性。最后,打印日志。示例用法仅供演示目的。在实践中,这些更可能是变量。结果看起来很棒:
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() 函数接受任意数量的集合,并获取它们的交集。它使用第一个集合来初始化公共集合。然后,使用公共集合与其他集合进行交集运算。结果如下。
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)
以下是结果:
在这个函数中,利用 *args 提供了最大的灵活性,因为我们不需要担心路径组件的数量。它还提高了代码的可读性,因为我们不需要将这些组件放入任何集合类型的变量中。
3.2 最大值和最小值
我想到的最简单的示例就是 min() 和 max() 函数。
print(max(1, 2, 3, 4, 5))
print(min(1, 2, 3, 4, 5))
它们分别从 *args 中获取最大值和最小值,我们不需要传递一个列表给它。顺便说一句,我们可能会发现 max() 和 min() 实际上也支持可迭代的参数。看下面的示例。
max([1, 2, 3, 4, 5])
多么灵活!它考虑了两种情况,并支持了使用该函数的两种直观方式。如果想在代码中做类似的事情,一个简单的想法是检查 args[0]。如果它是一个列表,就使用该列表。否则,使用整个 args 并迭代它。
3.3 打印函数
我相信大多数人都知道 print() 函数也支持 *args 模式。当我们使用此模式时,字符串的默认分隔符将是空格。
print("Towards", "Data", "Science")
print("Towards", "Data", "Science", sep=" | ")
在第二个示例中,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
本文仅供学习交流使用,如有侵权请联系作者删除