我认为在这种情况下,我们可以通过将布尔表达式的求值委托给它自己的函数来从函数式编程中获得一些灵感。这样,如果您需要更改布尔条件的行为,您只需更改该函数定义!
假设您要检查子列表和 int,它们恰好位于顶层。我们可以定义一个在对单个列表元素进行比较时返回布尔值的函数:
def elem(a, b):
'''
Defines if an object b matches a.
'''
return (isinstance(b, int) and a == b) or (isinstance(b, list) and a in b)
请注意,这个函数对我们的列表没有任何说明——我们使用的参数b 只是列表中的一个元素,但我们可以很容易地调用它来比较两个值。现在我们有以下内容:
>>> a = [[1,2],[3,4],[5,6],7,8,9]
>>> any(elem(2, i) for i in a)
True
>>> any(elem(8, i) for i in a)
True
>>> any(elem(10, i) for i in a)
False
宾果!这种定义的另一个好处是它允许您部分应用函数,并让您能够指定名称以仅搜索一种类型的数字:
from functools import partial
>>> contains2 = partial(elem, 2)
>>> any(map(contains2, a))
True
>>> b = [[1],[3,4],[5,6],7,8,9]]
>>> any(map(contains2, b))
False
在我看来,这使得代码更具可读性,但代价是一些样板文件,并且需要知道 map 做了什么——因为你可以让你的变量名有意义,而不是临时列表理解变量的丛林。我并不特别关心函数式方法是否不那么 Pythonic - Python 是一种多范式语言,我认为这样看起来更好,简单明了。但这是个人选择 - 由您决定。
现在假设我们的情况发生了变化,我们现在只想检查子列表 - 这对于顶级事件来说是不够的。没关系,因为现在我们只需要更改elem 的定义即可。让我们看看:
def elem(a, b):
return isinstance(b, list) and a in b
我们刚刚删除了在b 是顶级整数的情况下匹配的可能性!如果我们现在运行:
>>> a = [[1,2],[3,4],[5,6],7,8,9,"a",["b","c"]]
>>> any(elem(2, i) for i in a)
True
>>> any(elem(8, i) for i in a)
False
我将说明最后一个示例,真正说明这种定义的强大程度。假设我们有一个任意深度嵌套的整数列表。我们如何检查一个整数是否在任何级别?
我们可以采用递归方法——根本不需要太多修改:
def elem(a, b):
return (isinstance(b, int) and a == b) or \
(isinstance(b, list) and any(map(partial(elem, a), b)))
因为我们使用了这个定义为作用于单个元素的递归定义,所以之前使用的所有代码行仍然有效:
>>> d = [1, [2, [3, [4, 5]]]]
>>> any(elem(1, i) for i in d)
True
>>> any(elem(4, i) for i in d)
True
>>> any(elem(10, i) for i in d)
False
>>> any(map(contains2, d))
True
当然,鉴于这个函数现在是递归的,我们真的可以直接调用它:
>>> elem(4, d)
True
但重点仍然是,这种模块化方法允许我们通过仅更改 elem 的定义而不触及我们的主脚本来更改功能,这意味着更少的类型错误和更快的重构。