问题:Python可以测试列表中多个值的成员资格吗?
我想测试列表中是否有两个或多个值具有成员资格,但结果却出乎意料:
>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)
那么,Python可以一次在列表中测试多个值的成员资格吗?结果是什么意思?
回答 0
这可以满足您的要求,并且几乎可以在所有情况下使用:
>>> 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的快速c
支持实现set
,但是两种情况下的基本算法都是相同的。
如果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)
这仅比替代方法(set(bigseq) >= set(bigsubseq)
,位于4.36以上的时间)快1.66倍。
因此,子集测试通常更快,但幅度并不惊人。另一方面,让我们看看何时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
for集为O(1),而in
sequence为O(n)。
另一方面,有时仅使用子集测试是值得的。如果您的测试项目已经存储在集中,则绝对可以这样做。否则,all
只会慢一点,并且不需要任何其他存储。它也可以与大型项目生成器一起使用,在这种情况下有时可以大大提高速度。
回答 1
另一种方法是:
>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True
回答 2
我敢肯定,in
它具有更高的优先级,,
因此您的语句被解释为'a', ('b' in ['b' ...])
,然后'a', True
由于该'b'
值在数组中而被求值。
请参阅先前的答案以了解如何做自己想做的事情。
回答 3
如果您要检查所有输入匹配项,
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
如果您想检查至少一场比赛,
>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
回答 4
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
回答 5
[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']]
[]
回答 6
我想说,我们甚至可以将那些方括号排除在外。
array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
回答 7
这里给出的两个答案都不会处理重复的元素。例如,如果要测试[1,2,2]是否是[1,2,3,4]的子列表,则两者都将返回True。那可能就是您的意思,但是我只是想澄清一下。如果要为[1,2,3,4]中的[1,2,2]返回false,则需要对两个列表进行排序,并在每个列表上检查带有移动索引的每个项目。for循环稍微复杂一点。
回答 8
没有lambdas,怎么能成为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)
回答 9
这是我的做法:
A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
do something