发布于 2026-01-06 2 阅读
0

Python 操作符模块的未知特性

Python 操作符模块的未知特性

乍一看,Python 的operator.dump 模块似乎并不那么引人注目。它包含许多用于算术和二进制运算的运算符函数,以及一些便捷的辅助函数。它们看起来或许并不那么实用,但只需使用其中几个函数,就能让你的代码运行速度更快、更简洁、更易读、功能更强大。因此,在本文中,我们将深入探索这个强大的 Python 模块,并充分利用其中包含的每一个函数。

用例

该模块的主要部分由封装/模拟基本 Python 运算符(例如 `&` +、`& <<` 或`&`)的函数组成not。您可能一开始并不清楚为什么需要或想要使用这些函数,毕竟可以直接使用运算符本身,所以我们先来讨论一下这些函数的一些使用场景。

你可能需要在代码中使用这些运算符的第一个原因是,当你需要将运算符传递给函数时:

def apply(op, x, y):
    return op(x, y)

from operator import mul
apply(mul, 3, 7)
# 21
Enter fullscreen mode Exit fullscreen mode

之所以需要这样做,是因为 Python 的运算符(+`&`、-`&`、`&`……)并非函数,因此不能直接传递给函数。相反,你可以传递operator模块中的版本。你可以轻松地实现一个包装函数来完成这项工作,但没人想为每个算术运算符都创建一个函数,对吧?此外,这样做还能带来更函数式的编程风格。

你可能还会想,我不需要operator模块,直接用lambda表达式就行了!没错,但这就是你应该使用这个模块的第二个原因。这个模块中的函数比 lambda 表达式更快。单次执行你可能感觉不到,但如果循环运行足够多次,就会产生显著的性能差异:

python -m timeit "(lambda x,y: x + y)(12, 15)"
10000000 loops, best of 3: 0.072 usec per loop
python -m timeit -s "from operator import add" "add(12, 15)"
10000000 loops, best of 3: 0.0327 usec per loop
Enter fullscreen mode Exit fullscreen mode

所以,如果你习惯于写类似这样的代码(lambda x,y: x + y)(12, 15),你可能需要切换到来operator.add(12, 15)稍微提高性能。

第三,也是对我来说最重要的原因,使用operator模块是可读性——这更多的是一种个人偏好。如果你lambda经常使用表达式,那么使用表达式可能更自然,但在我看来,在模块中使用函数通常比使用 lambda 表达式更易读operator。例如,考虑以下示例:

(lambda x, y: x ^ y)(7, 10)

from operator import xor
xor(7, 10)
Enter fullscreen mode Exit fullscreen mode

显然,第二种选择更易读。

最后,与 lambda 表达式不同,operator模块函数是可序列化的,这意味着它们可以被保存并在以后恢复。这看起来可能没什么用,但对于分布式和并行计算来说至关重要,因为它们需要能够在进程之间传递函数。

所有选项

正如我之前提到的,这个模块包含了所有 Python 算术运算符、位运算符和真值运算符的函数,以及一些额外的功能。函数与实际运算符之间的完整映射关系请参见文档中的表格

除了所有预期功能外,该模块还提供了相应的就地版本,实现了诸如a += b`or`之类的操作a *= b。如果您想使用这些版本,只需在基本版本前加上前缀即可i,例如iadd`or` imul

最后,operator您还会找到所有这些函数的带下划线的版本,例如 `f`__add__或 `g` __mod__。这些版本保留是为了兼容旧版本,建议使用不带下划线的版本。

除了所有实际的操作符之外,该模块还有一些其他实用功能。其中一个鲜为人知的length_hint函数可以用来大致了解迭代器的长度:

from operator import length_hint
iterator = iter([2, 4, 12, 5, 18, 7])
length_hint(iterator)
# 6
iterator.__length_hint__()
# 6
Enter fullscreen mode Exit fullscreen mode

我想强调一下这里的关键词很模糊——不要依赖这个值,因为它只是一个提示,并不能保证准确性。

我们还可以从该模块获取另一个便捷函数,该函数返回在countOf(a, b)出现的次数,例如:ba

from operator import countOf
countOf([1, 4, 7, 15, 7, 5, 4, 7], 7)
# 3
Enter fullscreen mode Exit fullscreen mode

最后一个简单的辅助函数是indexOf(a, b),它返回元素在b元素中首次出现的索引a

from operator import indexOf
indexOf([1, 4, 7, 15, 7, 5, 4, 7], 7)
# 2
Enter fullscreen mode Exit fullscreen mode

主要功能

除了运算符函数和上述几个实用函数之外,operator该模块还包含用于处理高阶函数的函数。这些函数是 `and`attrgetter和 `or`,itemgetter它们通常用作关键函数,通常与 ` sortedor`等函数一起使用itertools.groupby

为了了解它们的工作原理以及如何在代码中使用它们,让我们来看几个例子。

假设我们有一个字典列表,我们想按一个共同的键对它们进行排序。以下是实现方法itemgetter

rows = [
    {"name": "John", "surname": "Doe", "id": 2},
    {"name": "Andy", "surname": "Smith", "id": 1},
    {"name": "Joseph", "surname": "Jones", "id": 3},
    {"name": "Oliver", "surname": "Smith", "id": 4},
]

from operator import itemgetter
sorted_by_name = sorted(rows, key=itemgetter("surname", "name"))
# [{"name": "John", "surname": "Doe", "id": 2},
#  {"name": "Joseph", "surname": "Jones", "id": 3},
#  {"name": "Andy", "surname": "Smith", "id": 1},
#  {"name": "Oliver", "surname": "Smith", "id": 4}]

min(rows, key=itemgetter("id"))
# {"name": "Andy", "surname": "Smith", "id": 1}
Enter fullscreen mode Exit fullscreen mode

在这个代码片段中,我们使用了sorted一个接受可迭代对象和键函数的函数。这个键函数必须是一个可调用对象,它从可迭代对象中获取单个元素rows,并提取用于排序的值。在本例中,我们传入一个参数,itemgetter该参数会自动创建可调用对象。我们还为其提供字典键,rows这些键随后会传递给对象的查找函数__getitem__,查找结果将用于排序。您可能已经注意到,我们同时使用了 `sort`surnamename`sort` 函数,这样我们就可以同时对多个字段进行排序。

代码片段的最后几行还显示了 的另一种用法itemgetter,即查找 ID 字段值最小的行。

接下来是attrgetter排序函数,其用途与itemgetter上述类似。更具体地说,我们可以用它来对不支持原生比较的对象进行排序:

class Order:
    def __init__(self, order_id):
        self.order_id = order_id

    def __repr__(self):
        return f"Order({self.order_id})"

orders = [Order(23), Order(6), Order(15) ,Order(11)]
from operator import attrgetter
sorted(orders, key=attrgetter("order_id"))
# [Order(6), Order(11), Order(15), Order(23)]
Enter fullscreen mode Exit fullscreen mode

这里我们使用self.order_id属性按订单 ID 进行排序。

以上两个函数与itertools模块中的一些函数结合使用非常有用,所以让我们看看如何使用它们itemgetter按字段对元素进行分组:

orders = [
    {"date": "07/10/2021", "id": 10001},
    {"date": "07/10/2021", "id": 10002},
    {"date": "07/12/2021", "id": 10003},
    {"date": "07/15/2021", "id": 10004},
    {"date": "07/15/2021", "id": 10005},
]

from operator import itemgetter
from itertools import groupby

orders.sort(key=itemgetter("date"))
for date, rows in groupby(orders, key=itemgetter("date")):
    print(f"On {date}:")
    for order in rows:
        print(order)
    print()

# On 07/10/2021:
# {"date": "07/10/2021", "id": 10001}
# {"date": "07/10/2021", "id": 10002}
# On 07/12/2021:
# {"date": "07/12/2021", "id": 10003}
# On 07/15/2021:
# {"date": "07/15/2021", "id": 10004}
# {"date": "07/15/2021", "id": 10005}
Enter fullscreen mode Exit fullscreen mode

这里我们有一个行列表(orders),我们想要按date字段对其进行分组。为此,我们首先对数组进行排序,然后调用函数groupby将具有相同值的项分组date。如果您想知道为什么需要先对数组进行排序,这是因为groupby该函数的工作原理是查找具有相同值的连续记录,因此需要预先将所有日期相同的记录分组在一起。

在前面的例子中,我们使用的是字典数组,但这些函数也可以应用于其他可迭代对象。例如,我们可以使用这些函数itemgetter按值对字典进行排序,查找数组中最小值/最大值的索引,或者根据元组的某些字段对元组列表进行排序:

# Sort dict by value
from operator import itemgetter
products = {"Headphones": 55.90, "USB drive": 12.20, "Ethernet Cable": 8.12, "Smartwatch": 125.80}

sort_by_price = sorted(products.items(), key=itemgetter(1))
# [('Ethernet Cable', 8.12), ('USB drive', 12.2), ('Headphones', 55.9), ('Smartwatch', 125.8)]

# Find index of maximum value in array
prices = [55.90, 12.20, 8.12, 99.80, 18.30]
index, price = max(enumerate(prices), key=itemgetter(1))
# 3, 99.8

# Sort list of tuples based on their indices
names = [
    ("John", "Doe"),
    ("Andy", "Jones"),
    ("Joseph", "Smith"),
    ("Oliver", "Smith"),
]

sorted(names, key=itemgetter(1, 0))
# [("John", "Doe"), ("Andy", "Jones"), ("Joseph", "Smith"), ("Oliver", "Smith")]
Enter fullscreen mode Exit fullscreen mode

方法调用者

模块中最后一个operator需要提及的函数是methodcaller。此函数可用于通过提供字符串形式的对象名称来调用对象上的方法:

from operator import methodcaller

methodcaller("rjust", 12, ".")("some text")
# "...some text"

column = ["data", "more data", "other value", "another row"]
[methodcaller("rjust", 12, ".")(value) for value in column]
# ["........data", "...more data", ".other value", ".another row"]
Enter fullscreen mode Exit fullscreen mode

在上面的第一个示例中,我们实际上使用 ` right-justify- methodcallerstring` 函数将"some text".rjust(12, ".")字符串右对齐到 12 个字符的长度,并使用 ` .fill-char` 作为填充字符。

例如,当您有一个所需方法的字符串名称,并且想要一遍又一遍地向其提供相同的参数时,使用此函数就更有意义,如上面的第二个示例所示。

另一个更实际的用法示例methodcaller是以下代码。这里我们将文本文件中的行传递给map一个函数,同时还传递了我们想要的方法——在本例中strip,该方法会从每一行中去除空格。此外,我们将该方法的结果传递给另一个函数,该函数会filter删除所有空行(空行是空字符串,其值为假值,因此会被过滤器删除)。

from operator import methodcaller

with open(path) as file:
    items = list(filter(None, map(methodcaller("strip"), file.read().splitlines())))
    print(items)
Enter fullscreen mode Exit fullscreen mode

结语

本文简要介绍了一个(在我看来)被低估的operator模块。这表明,即使是只有几个函数的小型模块,也能在日常 Python 编程任务中发挥重要作用。Python 标准库中还有许多其他有用的模块,因此我建议您查看模块索引并深入探索。您还可以查看我之前的文章,其中探讨了诸如itertoolsfunctools等模块。

文章来源:https://dev.to/martinheinz/the-unknown-features-of-python-s-operator-module-23p4