geekdoc-python-zh/docs/realpython/python-all.md

39 KiB
Raw Blame History

Python 的 all():检查你的 Iterables 的真实性

原文:https://realpython.com/python-all/

在编程时,你经常需要检查一个 iterable 中的所有项是否都是真实的。重复编写这种功能的代码可能会很烦人而且效率很低。幸运的是Python 提供了内置的all()函数来解决这个问题。这个函数接受一个 iterable 并检查它的所有项的真值,这对于发现这些项是否具有给定的属性或满足特定的条件很方便。

Python 的all()是一个强大的工具,可以帮助你用 Python 编写干净、可读、高效的代码。

在本教程中,您将学习如何:

  • 使用 all() 检查一个 iterable 中的所有项是否为真
  • all()与不同的可迭代类型一起使用
  • 结合all()的理解生成器表达式
  • 区分all()和布尔运算符and

为了补充这些知识,您将编写几个例子来展示令人兴奋的all()用例,并强调在 Python 编程中使用该函数的许多方式。

为了理解本教程中的主题,您应该对几个 Python 概念有基本的了解,比如可迭代的数据结构布尔类型表达式操作符列表理解,以及生成器表达式

免费 PDF 下载: Python 3 备忘单

评估项目的真值

编程中一个很常见的问题是确定一个列表或数组中的所有元素是否都是真的。例如,您可能有以下条件列表:

  • 5 > 2
  • 1 == 1
  • 42 < 50

为了确定这些条件是否为真,您需要迭代它们并测试每个条件的真实性。在这个例子中,你知道5 > 2为真,1 == 1为真,42 < 50也为真。因此,你可以说所有这些条件都是真实的。如果至少有一个条件为假,那么你会说不是所有的条件都为真。

注意,一旦你发现一个假条件,你可以停止评估条件,因为在这种情况下,你已经知道最终结果:不是所有的都是真的。

要通过编写定制的 Python 代码来解决这个问题,您可以使用一个 for循环来迭代每个条件并评估其真实性。您的循环将进行迭代,直到找到一个错误项,这时它将停止,因为您已经有了一个结果:

>>> def all_true(iterable):
...     for item in iterable:
...         if not item:
...             return False
...     return True
...

这个函数以一个可迭代作为参数。循环迭代输入参数,同时条件if语句使用 not 运算符检查是否有任何项目为 falsy。如果一个项目是假的那么函数立即返回 False,表明不是所有的项目都是真的。否则返回True

这个函数非常通用。它需要一个 iterable这意味着你可以传入一个列表,元组字符串字典,或者任何其他的 iterable 数据结构。为了检查当前项目是真还是假,all_true()使用not运算符来反转其操作数的真值。换句话说,如果它的操作数计算结果为假,它将返回True,反之亦然。

Python 的布尔操作符可以计算表达式和对象的真值,这保证了你的函数可以接受包含对象、表达式或两者的可迭代对象。例如,如果您传入一个布尔表达式的 iterable那么not只对表达式求值并对结果求反。

下面是all_true()的行动:

>>> bool_exps = [
...     5 > 2,
...     1 == 1,
...     42 < 50,
... ]
>>> all_true(bool_exps)
True

因为输入 iterable 中的所有表达式都为真,not对结果求反,if代码块永远不会运行。在这种情况下,all_true()返回True

当输入 iterable 包含 Python 对象和非布尔表达式时,也会发生类似的情况:

>>> objects = ["Hello!", 42, {}]
>>> all_true(objects)
False

>>> general_expressions = [
...     5 ** 2,
...     42 - 3,
...     int("42")
... ]
>>> all_true(general_expressions)
True

>>> empty = []
>>> all_true(empty)
True

在第一个例子中,输入列表包含常规的 Python 对象,包括一个字符串、一个和一个字典。在这种情况下,all_true()返回False,因为字典是空的,在 Python 中计算结果为 false。

为了对对象执行真值测试Python 为评估为假的对象提供了一组内部规则:

当您在 Python 中测试其他任何对象的真值时,它的值都为 true。

在第二个示例中,输入列表包含一般的 Python 表达式如数学表达式和函数调用。在这种情况下Python 首先计算表达式以获得其结果值,然后检查该值的真实性。

第三个例子突出了all_true()的一个重要细节。当输入 iterable 为空时,for循环不运行,函数立即返回True。这种行为乍一看似乎很奇怪。然而,其背后的逻辑是,如果输入 iterable 中没有条目,那么就没有办法判断任何条目是否为 falsy。因此该函数返回空的 iterables。

尽管用 Python 编写all_true()代码非常简单,但每次需要它的功能时都要编写这个自定义函数,这可能很烦人。确定 iterable 中的所有项是否都为真是编程中的一项常见任务Python 为此提供了内置的 all() 函数。

Remove ads

Python 的all() 入门

如果您查看 Python 的all()文档,那么您会注意到该函数与您在上一节中编写的函数是等效的。然而,像所有内置函数一样,all()是一个 C 函数,并针对性能进行了优化。

提出了all()any()函数,力图从 Python 中去掉 functools.reduce() 等功能工具,如 filter()map() 。然而Python 社区对移除这些工具并不满意。即便如此,all()any()还是作为内置函数被添加到 Python 2.5 中,由 Raymond Hettinger 实现。

可以说 Python 的all()执行了一个归约或折叠操作,因为它将一个 iterable items 项归约为一个单独的对象。然而,它不是一个高阶函数,因为它不将其他函数作为参数来执行其计算。因此,您可以将all()视为常规的谓词布尔值函数

您可以使用all()来检查输入 iterable 中的所有项是否都为真。因为是内置函数,所以不需要导入。它是这样工作的:

>>> bool_exps = [
...     5 > 2,
...     1 == 1,
...     42 < 50,
... ]
>>> all(bool_exps)
True

>>> objects = ["Hello!", 42, {}]
>>> all(objects)
False

>>> general_exps = [
...     5 ** 2,
...     42 - 3,
...     int("42")
... ]
>>> all(general_exps)
True

>>> empty = []
>>> all(empty)
True

这些例子显示了all()与您的自定义函数all_true()的工作原理相同。在内部,all()循环遍历输入 iterable 中的条目,检查它们的真值。如果它发现一个错误的条目,那么它返回False。否则,它返回True

如果你用一个空的 iterable 调用all(),就像你在上面最后一个例子中所做的那样,那么你会得到True,因为在一个空的 iterable 中没有 falsy 项。注意,all()评估的是输入 iterable 中的项,而不是 iterable 本身。参见如果 Iterable 为空,为什么all()返回True关于这一点的更多哲学讨论。

为了总结all()的行为,下面是它的真值表:

情况 结果
所有项目评估为真。 True
所有项目评估为假。 False
一个或多个项目评估为假。 False
输入 iterable 为空。 True

您可以运行以下对all()的调用来确认该表中的信息:

>>> all([True, True, True])
True

>>> all([False, False, False])
False

>>> all([False, True, True])
False

>>> all([])
True

这些例子表明,当输入 iterable 中的所有项都为真或者 iterable 为空时,all()返回True。否则,函数返回False

就像你的all_true()函数一样,all()也实现了所谓的短路评估。这种评估意味着all()一旦确定了行动的最终结果,就会立刻返回。

当函数在 iterable 中找到一个错误的项时,就会发生短路。在这种情况下,没有必要评估其余的项目,因为函数已经知道最终结果。请注意,这种类型的实现意味着当您测试具有副作用的条件时,您可以获得不同的行为。考虑下面的例子:

>>> def is_true(value):
...     print("Side effect!")
...     return bool(value)
...

>>> values = [0, 1]

>>> conditions = (is_true(n) for n in values)
>>> all(conditions)
Side effect!
False

>>> conditions = (is_true(n) for n in reversed(values))
>>> all(conditions)
Side effect!
Side effect!
False

is_true()函数将一个对象作为参数,并返回其真值。在函数的执行过程中,一个副作用发生了:函数的东西打印到屏幕上。

conditions的第一个实例保存了一个生成器表达式,它在对来自输入 iterable(在本例中为values)的每一项进行惰性求值之后产生真值。这次,all()只对函数求值一次,因为is_true(0)返回False。副作用只会出现一次。

现在来看看conditions的第二个实例。如果你反转输入的 iterable那么all()会评估两个条目,因为用1作为参数对is_true()的调用会返回True。副作用会持续两次。

这种行为可能是微妙问题的来源,因此您应该避免在代码中评估具有副作用的条件。

最后,当谈到使用all()函数时,可以说它至少有两个通用用例。您可以使用all()来检查 iterable 中的所有项目:

  1. 评估为真
  2. 具有给定的属性或者满足一定的条件

在下一节中,您将学习如何在 Python 中对不同的可迭代类型使用all()。之后,您将学习如何使用all()列表理解以及生成器表达式来解决上面列出的第二个用例。

Remove ads

all()用于不同的可迭代类型

内置的all()函数包含了 Python 的鸭子类型风格,并且接受不同的参数类型,只要它们是可迭代的。您可以将all()用于列表、元组、字符串、字典、等。

在所有情况下,all()都按预期工作,如果所有项目都正确,则返回True,否则返回False。在本节中,您将使用不同的可迭代类型编写使用all()的示例。

序列

至此,您已经了解了all()如何使用 Python 列表。在本节中,您将了解到列表和其他序列数据类型之间没有真正的区别,例如元组和 range 对象。函数所需要的就是输入对象是可迭代的。

下面是一些将all()用于元组和range对象的例子:

>>> # With tuples
>>> all((1, 2, 3))
True
>>> all((0, 1, 2, 3))
False
>>> all(())
True
>>> all(tuple())
True

>>> # With range objects
>>> all(range(10))
False
>>> all(range(1, 11))
True
>>> all(range(0))
True

通常,如果输入 iterable 中的所有项都是真的,那么您将得到True。否则,你得到False。空元组和范围产生一个True结果。在最后一个例子中,用0作为参数调用range()会返回一个空的range对象,因此all()会给出结果True

还可以将包含表达式、布尔表达式或任何类型的 Python 对象的元组传递给all()。来吧,试一试!

字典

字典是键值对的集合。如果你直接遍历字典,那么你会自动遍历它的键。此外,您可以使用方便的方法显式迭代字典的键、值和项。

**注:**用字典的.items()方法使用all()没有多大意义。该方法以两项元组的形式返回键-值对,在 Python 中这些元组的值总是为 true。

如果您将字典直接传递给all(),那么该函数将自动检查字典的键:

>>> all({"gold": 1, "silver": 2, "bronze": 3})
True

>>> all({0: "zero", 1: "one", 2: "two"})
False

因为第一个字典中的所有键都是真的,所以结果是得到True。在第二个字典中,第一个键是0,其值为 false。在这种情况下您从all()处取回False

如果您想获得与上面示例相同的结果,但是代码更可读、更显式,那么您可以使用 .keys() 方法,该方法从底层字典返回所有键:

>>> medals = {"gold": 1, "silver": 2, "bronze": 3}
>>> all(medals.keys())
True

>>> numbers = {0: "zero", 1: "one", 2: "two"}
>>> all(numbers.keys())
False

使用.keys(),您可以明确您的代码调用all()来确定输入字典中的所有当前键是否都是真的。

另一个常见的需求是,您需要检查给定字典中的所有值是否都评估为 true。在这种情况下可以使用 .values() :

>>> monday_inventory = {"book": 2, "pencil": 5, "eraser": 1}
>>> all(monday_inventory.values())
True

>>> tuesday_inventory = {"book": 2, "pencil": 3, "eraser": 0}
>>> all(tuesday_inventory.values())
False

在这些例子中,你首先检查你当前的学习用品库存中是否至少有一件物品。星期一,你所有的项目至少有一个单位,所以all()返回True。然而,在星期二,对all()的调用返回False,因为您已经用完了至少一种供应品中的单位,在本例中是eraser

Remove ads

all()用于理解和生成器表达式

正如您之前了解到的Python 的all()的第二个用例是检查 iterable 中的所有项是否都有给定的属性或满足特定的条件。为了进行这种检查,您可以使用带有列表理解或生成器表达式的all()作为参数,这取决于您的需要。

通过将all()与列表理解和生成器表达式相结合,您获得的协同效应释放了这个函数的全部能力,并使它在您的日常编码中非常有价值。

利用all()这种超级能力的一种方法是使用谓词函数来测试所需的属性。这个谓词函数将是 list comprehension 中的表达式,您将把它作为参数传递给all()。下面是所需的语法:

all([predicate(item) for item in iterable])

这个列表理解使用predicate()来测试给定属性的iterable中的每个item。然后对all()的调用将结果列表缩减为一个单独的TrueFalse值,这将告诉您是否所有的条目都具有predicate()定义和测试的属性。

例如,下面的代码检查序列中的所有值是否都是质数:

>>> import math

>>> def is_prime(n):
...     if n <= 1:
...         return False
...     for i in range(2, math.isqrt(n) + 1):
...         if n % i == 0:
...             return False
...     return True
...

>>> numbers = [2, 3, 5, 7, 11]
>>> all([is_prime(x) for x in numbers])
True

>>> numbers = [2, 4, 6, 8, 10]
>>> all([is_prime(x) for x in numbers])
False

在这个例子中,您将all()与列表理解结合起来。理解使用is_prime()谓词函数来测试numbers中的每个值的素性。结果列表将包含每次检查结果的布尔值(TrueFalse)。然后all()获取这个列表作为参数,并处理它以确定所有的数字是否都是质数。

注意:is_prime()谓词基于维基百科关于素性测试的文章中的算法。

这个神奇组合的第二个用例,all()加上一个列表理解,是检查 iterable 中的所有条目是否满足给定的条件。下面是所需的语法:

all([condition for item in iterable])

这个对all()的调用使用一个列表理解来检查iterable中的所有项目是否满足所需的condition,这通常是根据单个item来定义的。按照这个想法,下面有几个例子来检查列表中的所有数字是否都大于0:

>>> numbers = [1, 2, 3]
>>> all([number > 0 for number in numbers])
True

>>> numbers = [-2, -1, 0, 1, 2]
>>> all([number > 0 for number in numbers])
False

在第一个例子中,all()返回True,因为输入列表中的所有数字都满足大于0的条件。在第二个例子中,结果是False,因为输入 iterable 包含0和负数。

正如您已经知道的,all()返回带有空 iterable 作为参数的True。这种行为可能看起来很奇怪,并可能导致错误的结论:

>>> numbers = []

>>> all([number < 0 for number in numbers])
True

>>> all([number == 0 for number in numbers])
True

>>> all([number > 0 for number in numbers])
True

这段代码显示numbers中的所有值都小于0,但是它们也等于并且大于0,这是不可能的。这种不合逻辑的结果的根本原因是所有这些对all()的调用都计算空的 iterables这使得all()返回True

要解决这个问题,您可以使用内置的 len()函数来获取输入 iterable 中的项数。如果len()返回0,那么你可以跳过调用all()来处理空的输入 iterable。这个策略将使你的代码不容易出错。

您在本节中编写的所有示例都使用列表理解作为all()的参数。列表理解在内存中创建一个完整的列表,这可能是一个浪费的操作。如果您的代码中不再需要结果列表,这种行为尤其成立,这是典型的all()情况。

在这种情况下,使用带有生成器表达式all()总是更有效,尤其是当你处理一个长输入列表时。生成器表达式不是在内存中构建一个全新的列表,而是根据需要生成条目,从而使您的代码更加高效。

构建生成器表达式的语法几乎与理解列表所用的语法相同:

# With a predicate
all(predicate(item) for item in iterable)

# With a condition
all(condition for item in iterable)

唯一的区别是生成器表达式使用括号(())而不是方括号([])。因为函数调用已经需要圆括号,所以只需要去掉方括号。

与列表理解不同,生成器表达式按需生成条目,这使得它们在内存使用方面非常有效。此外,你不会创建一个新的列表,然后在all()返回后扔掉它。

Remove ads

all()and布尔运算符进行比较

你可以大致把all()想象成通过布尔 and 运算符连接起来的一系列项目。例如,函数调用all([item1, item2, ..., itemN])在语义上等同于表达式item1 and item2 ... and itemN。然而,它们之间有一些微小的差异。

在本节中,您将了解这些差异。第一个与语法有关,第二个与返回值有关。此外,您将了解到all()and操作符都实现短路评估。

理解语法差异

all()的调用使用与 Python 中任何函数调用相同的语法。你需要用一对括号来调用这个函数。在all()的特定情况下,您必须传入一个值的 iterable 作为参数:

>>> all([True, False])
False

输入 iterable 中的项可以是通用表达式、布尔表达式或任何类型的 Python 对象。此外input iterable 中的项数只取决于系统中可用的内存量。

另一方面,and运算符是一个二元运算符,它连接表达式中的两个操作数:

>>> True and False
False

逻辑运算符and采用左操作数和右操作数来构建复合表达式。就像使用all()一样,and表达式中的操作数可以是通用表达式、布尔表达式或 Python 对象。最后,您可以使用多个and操作符来连接任意数量的操作数。

返回布尔值 vs 操作数

all()and操作符之间的第二个甚至更重要的区别是它们各自的返回值。当all()总是返回TrueFalse时,and操作符总是返回它的一个操作数。如果返回的操作数显式地评估为任一值,则它仅返回TrueFalse:

>>> all(["Hello!", 42, {}])
False
>>> "Hello!" and 42 and {}
{}

>>> all([1, 2, 3])
True
>>> 1 and 2 and 3
3

>>> all([0, 1, 2, 3])
False
>>> 0 and 1 and 2 and 3
0

>>> all([5 > 2, 1 == 1])
True
>>> 5 > 2 and 1 == 1
True

这些例子展示了all()如何总是返回TrueFalse,这与谓词函数的状态一致。另一方面,and返回最后计算的操作数。如果它恰好是一个表达式中的最后一个操作数,那么前面的所有操作数一定都是真的。否则,and将返回第一个 falsy 操作数,指示求值停止的位置。

注意,在最后一个例子中,and操作符返回True,因为隐含的操作数是比较表达式,它们总是显式返回TrueFalse

这是all()函数和and操作符之间的一个重要区别。因此,您应该考虑到这一点,以防止代码中出现微妙的错误。然而,在布尔上下文中,比如if语句和 while循环,这种差异根本不相关。

短路评估

正如您已经了解到的,all()在决定最终结果时,会缩短对输入 iterable 中各项的评估。and操作员还执行短路评估

此功能的优点是,一旦出现错误的项目,就跳过剩余的检查,从而提高操作效率。

要尝试短路评估,您可以使用发生器函数,如下例所示:

>>> def generate_items(iterable):
...     for i, item in enumerate(iterable):
...         print(f"Checking item: {i}")
...         yield item
...

generate_items()中的循环遍历iterable中的条目,使用内置的 enumerate() 函数获取每个选中条目的索引。然后,该循环打印一条标识选中物品的消息,并生成手边的物品。

有了generate_items(),您可以运行以下代码来测试all()的短路评估:

>>> # Check both items to get the result
>>> items = generate_items([True, True])
>>> all(items)
Checking item: 0
Checking item: 1
True

>>> # Check the first item to get the result
>>> items = generate_items([False, True])
>>> all(items)
Checking item: 0
False

>>> # Still have a remaining item
>>> next(items)
Checking item: 1
True

all()的第一次调用展示了该函数如何检查这两项以确定最终结果。第二次调用确认all()只检查第一项。由于这一项为 false sy所以该函数不检查第二项就立即返回。这就是为什么当你调用 next() 的时候,生成器还是会产生第二个项目。

现在您可以使用and操作符运行一个类似的测试:

>>> # Check both items to get the result
>>> items = generate_items([True, True])
>>> next(items) and next(items)
Checking item: 0
Checking item: 1
True

>>> # Check the first item to get the result
>>> items = generate_items([False, True])
>>> next(items) and next(items)
Checking item: 0
False

>>> # Still have a remaining item
>>> next(items)
Checking item: 1
True

第一个and表达式评估两个操作数以获得最终结果。第二个and表达式只计算第一个操作数来决定结果。用items作为参数调用next(),显示生成器函数仍然产生一个剩余项。

Remove ads

all()付诸行动:实例

到目前为止,您已经学习了 Python 的all()的基础知识。你已经学会了在序列、字典、列表理解和生成器表达式中使用它。此外,您已经了解了这个内置函数和逻辑操作符and之间的区别和相似之处。

在这一节中,您将编写一系列实际例子,帮助您评估在使用 Python 编程时all()有多有用。所以,请继续关注并享受您的编码吧!

提高长复合条件的可读性

all()的一个有趣的特性是,当您处理基于and操作符的长复合布尔表达式时,这个函数如何提高代码的可读性。

例如,假设您需要在一段代码中验证用户的输入。为了使输入有效,它应该是一个介于0100之间的整数,也是一个偶数。要检查所有这些条件,可以使用下面的if语句:

>>> x = 42

>>> if isinstance(x, int) and 0 <= x <= 100 and x % 2 == 0:
...     print("Valid input")
... else:
...     print("Invalid input")
...
Valid input

if条件包括对 isinstance() 的调用,用于检查输入是否为整数;一个链式比较表达式,用于检查数字是否在0100之间;以及一个表达式,用于检查输入值是否为偶数。

尽管这段代码可以工作,但是条件相当长,这使得解析和理解起来很困难。此外,如果您需要在未来的更新中添加更多的验证检查,那么条件将变得更长、更复杂。它还需要一些代码格式化。

为了提高这个条件的可读性,你可以使用all(),就像下面的代码:

>>> x = 42

>>> validation_conditions = (
...     isinstance(x, int),
...     0 <= x <= 100,
...     x % 2 == 0,
... )

>>> if all(validation_conditions):
...     print("Valid input")
... else:
...     print("Invalid input")
...
Valid input

在这个例子中,所有的验证条件都存在于一个具有描述性名称的元组中。使用这种技术还有一个额外的好处:如果您需要添加一个新的验证条件,那么您只需要向您的validation_conditions元组添加一个新行。请注意,现在您的if语句拥有了一个基于all()的非常易读、明确和简洁的表达式。

在现实生活中,验证策略通常允许您重用验证代码。例如,您可以编写可重用的验证函数,而不是指定只计算一次的普通条件:

>>> def is_integer(x):
...     return isinstance(x, int)
...

>>> def is_between(a=0, b=100):
...     return lambda x: a <= x <= b
...

>>> def is_even(x):
...     return x % 2 == 0
...

>>> validation_conditions = (
...     is_integer,
...     is_between(0, 100),
...     is_even,
... )

>>> for x in (4.2, -42, 142, 43, 42):
...     print(f"Is {x} valid?", end=" ")
...     print(all(condition(x) for condition in validation_conditions))
...
Is 4.2 valid? False
Is -42 valid? False
Is 142 valid? False
Is 43 valid? False
Is 42 valid? True

在这个例子中,有三个函数以可重用的方式检查三个初始条件。然后,使用刚刚编写的函数重新定义验证条件元组。最后的for循环展示了如何使用all()重用这些函数来验证几个输入对象。

验证数值的可重复项

all()的另一个有趣的用例是检查一个 iterable 中的所有数值是否都在给定的区间内。下面是几个示例,说明如何在不同的条件下,借助生成器表达式来实现这一点:

>>> numbers = [10, 5, 6, 4, 7, 8, 20]

>>> # From 0 to 20 (Both included)
>>> all(0 <= x <= 20 for x in numbers)
True

>>> # From 0 to 20 (Both excluded)
>>> all(0 < x < 20 for x in numbers)
False

>>> # From 0 to 20 (integers only)
>>> all(x in range(21) for x in numbers)
True

>>> # All greater than 0
>>> all(x > 0 for x in numbers)
True

这些例子展示了如何构建生成器表达式来检查一个可迭代数字中的所有值是否都在给定的区间内。

上面例子中的技术允许很大的灵活性。您可以调整条件并使用all()在目标 iterable 上运行各种检查。

Remove ads

验证字符串和字符串的可重复项

内置的 str 类型实现了几个谓词字符串方法,当您需要验证字符串的可重复项和给定字符串中的单个字符时,这些方法会很有用。

例如,使用这些方法,您可以检查一个字符串是否是有效的十进制数,是否是字母数字字符,或者是否是有效的 ASCII 字符。

下面是一些在代码中使用字符串方法的示例:

>>> numbers = ["1", "2", "3.0"]

>>> all(number.isdecimal() for number in numbers)
True

>>> chars = "abcxyz123"

>>> all(char.isalnum() for char in chars)
True

>>> all(char.isalpha() for char in chars)
False

>>> all(char.isascii() for char in chars)
True

>>> all(char.islower() for char in chars)
False

>>> all(char.isnumeric() for char in chars)
False

>>> all(char.isprintable() for char in chars)
True

这些.is*()方法中的每一个都检查底层字符串的特定属性。您可以利用这些和其他几个 string 方法来验证可重复字符串中的项以及给定字符串中的单个字符。

从表格数据中删除带有空字段的行

当您处理表格数据时,可能会遇到空字段的问题。您可能需要清理包含空字段的行。如果是这种情况,那么您可以使用all()filter() 来提取所有字段中都有数据的行。

内置的filter()函数以一个函数对象和一个 iterable 作为参数。通常,您将使用谓词函数作为filter()的第一个参数。对filter()的调用将谓词应用于 iterable 中的每一项,并返回一个迭代器,其中包含使谓词返回True的项。

您可以在filter()调用中使用all()作为谓词。这样,您可以处理列表的列表,这在您处理表格数据时会很有用。

举一个具体的例子,假设您有一个 CSV 文件,其中包含关于您公司员工的数据:

name,job,email
"Linda","Technical Lead","" "Joe","Senior Web Developer","joe@example.com"
"Lara","Project Manager","lara@example.com"
"David","","david@example.com" "Jane","Senior Python Developer","jane@example.com"

快速浏览一下这个文件,您会注意到有些行包含空字段。例如,第一行没有电子邮件,第四行没有提供职位或角色。您需要通过删除包含空字段的行来清理数据。

下面是如何通过在一个filter()调用中使用all()作为谓词来满足这个需求:

>>> import csv
>>> from pprint import pprint

>>> with open("employees.csv", "r") as csv_file:
...     raw_data = list(csv.reader(csv_file))
...

>>> # Before cleaning
>>> pprint(raw_data)
[['name', 'job', 'email'],
 ['Linda', 'Technical Lead', ''], ['Joe', 'Senior Web Developer', 'joe@example.com'],
 ['Lara', 'Project Manager', 'lara@example.com'],
 ['David', '', 'david@example.com'], ['Jane', 'Senior Python Developer', 'jane@example.com']]

>>> clean_data = list(filter(all, raw_data)) 
>>> # After cleaning
>>> pprint(clean_data)
[['name', 'job', 'email'],
 ['Joe', 'Senior Web Developer', 'joe@example.com'],
 ['Lara', 'Project Manager', 'lara@example.com'],
 ['Jane', 'Senior Python Developer', 'jane@example.com']]

在这个例子中,首先使用 Python 标准库中的 csv 模块将目标 CSV 文件的内容加载到raw_data中。对 pprint() 函数的调用显示,数据包含带有空字段的行。然后你用filter()all()清理数据。

**注意:**如果你觉得用filter()不舒服,那么你可以用列表理解来代替。

继续运行下面的代码行:

>>> clean_data = [row for row in raw_data if all(row)]

一旦有了干净数据的列表,就可以再次运行for循环来检查是否一切正常。

filter()all()函数如何协同执行任务?嗯,如果all()在一行中找到一个空字段,那么它返回False。因此,filter()不会将该行包含在最终数据中。为了确保这种技术有效,您可以使用干净的数据作为参数来调用pprint()

比较自定义数据结构

作为如何使用all()的另一个例子,假设您需要创建一个定制的类似列表的类,它允许您检查它的所有值是否都大于一个特定值。

要创建这个自定义类,您可以从 collections 模块中子类化 UserList ,然后覆盖被称为 .__gt__()特殊方法。覆盖这个方法允许您重载大于(>)操作符,为它提供一个自定义行为:

>>> from collections import UserList

>>> class ComparableList(UserList):
...     def __gt__(self, threshold):
...         return all(x > threshold for x in self)
...

>>> numbers = ComparableList([1, 2, 3])

>>> numbers > 0
True

>>> numbers > 5
False

.__gt__()中,您使用all()来检查当前列表中的所有数字是否都大于应该来自用户的特定threshold值。

这段代码末尾的比较表达式展示了如何使用您的自定义列表,以及它如何与大于号(>)操作符一起工作。在第一个表达式中,列表中的所有值都大于0,所以结果是True。在第二个表达式中,所有的数字都小于5,这导致了一个False结果。

Remove ads

部分模拟 Python 的zip()函数

Python 内置的 zip() 函数对于并行循环多个可迭代对象很有用。该函数将给定数量的可重复项( N )作为参数,并将每个可重复项中的元素聚合到 N 项元组中。在这个例子中,您将学习如何使用all()来部分模拟这个功能。

为了更好地理解这一挑战,请查看zip()的基本功能:

>>> numbers = zip(["one", "two"], [1, 2])

>>> list(numbers)
[('one', 1), ('two', 2)]

在这个例子中,您将两个列表作为参数传递给zip()。该函数返回一个迭代器,每个迭代器产生两个条目的元组,您可以通过调用list()将结果迭代器作为参数来确认。

这里有一个模拟这种功能的函数:

>>> def emulated_zip(*iterables):
...     lists = [list(iterable) for iterable in iterables]
...     while all(lists):
...         yield tuple(current_list.pop(0) for current_list in lists)
...

>>> numbers = emulated_zip(["one", "two"], [1, 2])

>>> list(numbers)
[('one', 1), ('two', 2)]

您的emulated_zip()函数可以接受由可迭代对象组成的可变数量的参数。函数中的第一行使用 list comprehension 将每个输入 iterable 转换成 Python 列表,以便您稍后可以使用它的.pop()方法。循环条件依赖于all()来检查所有的输入列表是否至少包含一个条目。

在每次迭代中, yield 语句从每个输入列表中返回一个包含一个条目的元组。以0为参数调用.pop()从每个列表中检索并移除第一个项目。

一旦循环迭代的次数足够多,以至于.pop()从列表中删除了所有的条目,那么条件就变为假,函数就终止了。当最短的 iterable 用尽时,循环结束,截断较长的 iterable。该行为与zip()的默认行为一致。

请注意,您的函数只是部分模拟了内置的zip()函数,因为您的函数没有采用strict参数。这个参数是在 Python 3.10 中添加的,作为处理不相等长度的可重复项的一种安全方式。

结论

现在您知道了如何使用 Python 的内置函数all()来检查现有 iterable 中的所有项是否都是真的。您还知道如何使用这个函数来确定 iterable 中的项是否满足给定的条件或者是否具有特定的属性。

有了这些知识,您现在就能够编写可读性更强、效率更高的 Python 代码了。

在本教程中,您学习了:

  • 如何使用 Python 的 all() 检查一个 iterable 中的所有项是否为真
  • all()如何处理不同的可迭代类型
  • 如何将all()结合起来理解生成器表达式
  • 是什么使得all()and运算符有所不同和相似

此外,您编写了几个实际例子,帮助您理解all()有多强大,以及它在 Python 编程中最常见的一些用例是什么。

免费 PDF 下载: Python 3 备忘单*******