Python: 检查对象是否存在于生成器中
我遇到了一个问题,是因为检查“如果 x 在生成器中”的结果会变化。
def primes(upper_limit):
for n in range(2, upper_limit):
if all(n % i > 0 for i in range(2, n)):
yield n
first_hundred_primes = primes(100)
print(5 in first_hundred_primes)
print(5 in first_hundred_primes)
print(5 in first_hundred_primes)
print(5 in first_hundred_primes)
print(5 in first_hundred_primes)
这段代码的输出是:
True
False
False
False
False
我在想,检查一个对象是否在生成器中是不是不太对,但如果真是这样,为什么没有报错呢?而且为什么这样做会有效呢?
>>> hundred_generator = range(1,100)
>>> 50 in hundred_generator
True
>>> 50 in hundred_generator
True
>>> 50 in hundred_generator
True
我通常会在检查某个对象是否存在之前,把生成器转换成一个集合(这样检查会更快),这样就解决了问题。但我很想知道这里到底发生了什么?
1 个回答
9
当你遍历一个生成器里的元素时,你就把这些元素“消耗”掉了。
试试这个:
len(list(first_hundred_primes)) > 0
=> True
len(list(first_hundred_primes)) > 0
=> False
也就是说,当你第一次使用 in
来遍历这些元素时,你就已经把它们用完了,或者说你最多只会得到前5个元素,因此在那之后,生成器就不会再生成5了。第二次再用的时候,它就什么都不会生成了。
你可以选择:
- 在使用之前把生成器转换成一个列表(或集合):
first_hundred_primes = list(first_hundred_primes)
- 每次都创建一个新的生成器:
5 in primes(100); 5 in primes(100); ...
- 使用
itertools.tee
补充:
关于你问的 range
:range
不是一个生成器。
在python2中,它只是返回一个列表,这没问题。
在python3中,它返回一个看起来像集合的特殊对象。它不需要实际存储范围内的所有数字,而是根据定义范围的规则来实现列表操作。例如,len
是通过 stop-start
来实现的。因为它代表的是一个集合,而不是生成器,所以你可以多次遍历它,而不会“消耗”掉元素。