【发布时间】:2021-04-06 02:29:45
【问题描述】:
我想测试两个或多个值是否在一个列表中具有成员资格,但我得到了一个意外的结果:
>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)
那么,Python 可以同时测试一个列表中多个值的成员资格吗? 这个结果是什么意思?
【问题讨论】:
标签: python
我想测试两个或多个值是否在一个列表中具有成员资格,但我得到了一个意外的结果:
>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)
那么,Python 可以同时测试一个列表中多个值的成员资格吗? 这个结果是什么意思?
【问题讨论】:
标签: python
这可以满足您的需求,并且几乎适用于所有情况:
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True
表达式 'a','b' in ['b', 'a', 'foo', 'bar'] 无法按预期工作,因为 Python 将其解释为元组:
>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)
还有其他方法可以执行此测试,但它们不适用于多种不同类型的输入。正如Kabie 指出的那样,您可以使用集合来解决这个问题......
>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True
...有时:
>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
只能使用可散列元素创建集合。但是生成器表达式all(x in container for x in items) 几乎可以处理任何容器类型。唯一的要求是 container 是可重复的(即不是生成器)。 items 可以是任何可迭代的。
>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True
在许多情况下,子集测试会比all 更快,但差异并不令人震惊——除非问题不相关,因为集合不是一个选项。仅仅为了这样的测试而将列表转换为集合并不总是值得麻烦的。将生成器转换为集合有时会非常浪费,会使程序速度降低许多数量级。
以下是一些用于说明的基准。当container 和items 都相对较小时,最大的区别就出现了。在这种情况下,子集方法大约快一个数量级:
>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
这看起来有很大的不同。但只要 container 是一个集合,all 仍然可以在更大的范围内完美使用:
>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
使用子集测试仍然更快,但在此规模下仅快 5 倍。速度的提升是由于 Python 对set 的快速c 支持实现,但两种情况下的基本算法是相同的。
如果您的items 由于其他原因已经存储在列表中,那么您必须在使用子集测试方法之前将它们转换为集合。然后加速下降到大约 2.5 倍:
>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
如果你的container是一个序列,需要先转换,那么加速就更小了:
>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
只有当我们将container 作为序列保留时,才会得到极其缓慢的结果:
>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
当然,我们只会在必要时这样做。如果bigseq 中的所有项目都是可哈希的,那么我们将改为这样做:
>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
这仅比替代方案快 1.66 倍(set(bigseq) >= set(bigsubseq),以上时间为 4.36)。
因此子集测试通常更快,但幅度不大。另一方面,让我们看看all 何时更快。如果items 有一千万个值,并且很可能包含不在container 中的值怎么办?
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
在这种情况下,将生成器转换为集合被证明是非常浪费的。 set 构造函数必须消耗整个生成器。但是all 的短路行为确保只需要消耗生成器的一小部分,因此它比子集测试快四个数量级。
诚然,这是一个极端的例子。但正如它所显示的那样,您不能假设一种方法或另一种方法在所有情况下都会更快。
大多数时候,将container 转换为一个集合是值得的,至少如果它的所有元素都是可散列的。这是因为 in 对于集合是 O(1),而 in 对于序列是 O(n)。
另一方面,使用子集测试可能只是有时值得。如果您的测试项目已经存储在一组中,请务必这样做。否则,all 只会慢一点,并且不需要任何额外的存储空间。它也可以与大型项目生成器一起使用,有时在这种情况下会提供巨大的加速。
【讨论】:
另一种方法:
>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True
【讨论】:
set(['a', 'b']) <= set(['b','a','foo','bar']) 是同一事物的另一种拼写方式,看起来“更数学”。
{'a', 'b'} <= {'b','a','foo','bar'}
如果您想检查您输入的所有匹配项,
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
如果您想检查至少一个匹配项,
>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
【讨论】:
我很确定in 的优先级高于,,因此您的语句被解释为'a', ('b' in ['b' ...]),然后评估为'a', True,因为'b' 在数组中。
有关如何做你想做的事,请参阅上一个答案。
【讨论】:
Python 解析器将该语句评估为一个元组,其中第一个值为 'a',第二个值为表达式 'b' in ['b', 'a', 'foo', 'bar'](其计算结果为 True)。
你可以编写一个简单的函数来做你想做的事:
def all_in(candidates, sequence):
for element in candidates:
if element not in sequence:
return False
return True
然后这样称呼它:
>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True
【讨论】:
我想说我们甚至可以不使用方括号。
array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
【讨论】:
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
我认为这比选择的答案更好的原因是您确实不需要调用“all()”函数。在 IF 语句中,空列表评估为 False,非空列表评估为 True。
if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
...Do something...
例子:
>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]
【讨论】:
这里提供的两个答案都不会处理重复的元素。例如,如果您正在测试 [1,2,2] 是否是 [1,2,3,4] 的子列表,则两者都将返回 True。这可能是你的意思,但我只是想澄清一下。 如果要为 [1,2,3,4] 中的 [1,2,2] 返回 false,则需要对两个列表进行排序,并使用每个列表上的移动索引检查每个项目。只是稍微复杂一点的 for 循环。
【讨论】:
我是这样做的:
A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
do something
【讨论】:
如果没有 lambda,你怎么能成为 Pythonic! ..不要被当真..但这种方式也有效:
orig_array = [ ..... ]
test_array = [ ... ]
filter(lambda x:x in test_array, orig_array) == test_array
如果您想测试 任何 值是否在数组中,请省略结尾部分:
filter(lambda x:x in test_array, orig_array)
【讨论】:
filter 是生成器的 Python 3 中无法按预期工作。如果您想实际获得可以使用== 或在布尔上下文中测试的结果(以查看它是否为空),则需要将其包装在list 中。在any 或all 中使用列表推导式或生成器表达式更为可取。