有没有测试,如果发电机组没有项目,如一个简单的方法peek
,hasNext
,isEmpty
,类似的规定?
[]
很容易成为Falsey,因此您可以对其进行if检查,并针对某些问题或不进行任何特殊行为。生成器是正确的,即使它们不产生任何元素。
glob.iglob("filepattern")
用户提供的通配符模式,如果该模式与任何文件都不匹配,我想警告用户。当然,我可以通过各种方式解决此问题,但是能够干净地测试迭代器是否为空很有用。
有没有测试,如果发电机组没有项目,如一个简单的方法peek
,hasNext
,isEmpty
,类似的规定?
[]
很容易成为Falsey,因此您可以对其进行if检查,并针对某些问题或不进行任何特殊行为。生成器是正确的,即使它们不产生任何元素。
glob.iglob("filepattern")
用户提供的通配符模式,如果该模式与任何文件都不匹配,我想警告用户。当然,我可以通过各种方式解决此问题,但是能够干净地测试迭代器是否为空很有用。
Answers:
您问题的简单答案:不,没有简单的方法。有很多解决方法。
实际上,不应该有一种简单的方法,因为生成器是什么:一种在不将序列保存在内存中的情况下输出值序列的方法。因此,没有向后遍历。
您可以编写has_next函数,甚至可以将其作为带有精美装饰器的方法添加到生成器上。
建议:
def peek(iterable):
try:
first = next(iterable)
except StopIteration:
return None
return first, itertools.chain([first], iterable)
用法:
res = peek(mysequence)
if res is None:
# sequence is empty. Do stuff.
else:
first, mysequence = res
# Do something with first, maybe?
# Then iterate over the sequence:
for element in mysequence:
# etc.
return first, itertools.chain([first], rest)
。
def gen(): for pony in range(4): yield None if pony == 2 else pony
None
而是提高)StopIteration
,则函数的结果为None
。否则,它是一个元组,不是None
。
This method is for backward compatibility only. def next(self): """Return the next message in a one-time iteration.""" if not hasattr(self, '_onetime_keys'): self._onetime_keys = self.iterkeys() while True: try: return self[next(self._onetime_keys)] except StopIteration: return None except KeyError: continue
一种简单的方法是将可选参数用于next(),如果生成器用尽(或为空),则使用该参数。例如:
iterable = some_generator()
_exhausted = object()
if next(iterable, _exhausted) == _exhausted:
print('generator is empty')
编辑:更正了mehtunguh注释中指出的问题。
object()
,而不是class
让它一行短:_exhausted = object()
; if next(iterable, _exhausted) is _exhausted:
next(generator, None) is not None
或替换,None
但是无论您知道什么值都不在您的生成器中。
编辑:是的,这将跳过生成器中的1个项目。但是,通常我会检查生成器是否仅出于验证目的而为空,然后才真正不使用它。否则我会做类似的事情:
def foo(self):
if next(self.my_generator(), None) is None:
raise Exception("Not initiated")
for x in self.my_generator():
...
也就是说,如果您的生成器来自函数,则此方法有效,如中所述generator()
。
None
?
最好的方法,恕我直言,将避免特殊的测试。大多数情况下,使用发电机是一种测试:
thing_generated = False
# Nothing is lost here. if nothing is generated,
# the for block is not executed. Often, that's the only check
# you need to do. This can be done in the course of doing
# the work you wanted to do anyway on the generated output.
for thing in my_generator():
thing_generated = True
do_work(thing)
如果这还不够好,您仍然可以执行显式测试。此时,thing
将包含最后生成的值。如果未生成任何内容,则它将是未定义的-除非您已经定义了变量。您可以检查的值thing
,但这有点不可靠。相反,只需在块内设置一个标志,然后再检查它:
if not thing_generated:
print "Avast, ye scurvy dog!"
range(10000000)
是有限生成器(Python 3),但您无需遍历所有项目即可确定它是否生成了某些东西。
我讨厌提供第二种解决方案,尤其是我自己不会使用的解决方案,但是,如果您绝对必须这样做并且不消耗生成器,那么在其他答案中:
def do_something_with_item(item):
print item
empty_marker = object()
try:
first_item = my_generator.next()
except StopIteration:
print 'The generator was empty'
first_item = empty_marker
if first_item is not empty_marker:
do_something_with_item(first_item)
for item in my_generator:
do_something_with_item(item)
现在我真的不喜欢这种解决方案,因为我认为这不是生成器的使用方式。
我意识到该帖子目前已有5年历史了,但是我在寻找惯用的方法时发现了它,并且没有看到我的解决方案发布。因此,对于后代:
import itertools
def get_generator():
"""
Returns (bool, generator) where bool is true iff the generator is not empty.
"""
gen = (i for i in [0, 1, 2, 3, 4])
a, b = itertools.tee(gen)
try:
a.next()
except StopIteration:
return (False, b)
return (True, b)
当然,正如我敢肯定的,很多评论员都会指出,这很hacky,并且仅在某些有限的情况下才起作用(例如,生成器是无副作用的)。YMMV。
gen
为每个项目调用一次生成器,因此副作用并不是一个太大的问题。但是它将存储已通过b
,但不通过a
,从生成器中拉出的所有内容的副本,因此内存含义类似于仅运行list(gen)
和检查。
很抱歉使用明显的方法,但是最好的方法是:
for item in my_generator:
print item
现在,您已经检测到生成器在使用时是空的。当然,如果生成器为空,则将永远不会显示项目。
这可能并不完全适合您的代码,但这是生成器的惯用法:迭代,因此也许您可能会稍微改变方法,或者根本不使用生成器。
您需要查看生成器是否为空的所有方法是尝试获取下一个结果。当然,如果您还没有准备好使用该结果,则必须将其存储起来,以便以后再次返回。
这是一个包装器类,可以将其添加到现有迭代器中以添加__nonzero__
测试,因此您可以使用simple来查看生成器是否为空if
。它也可能会变成装饰器。
class GenWrapper:
def __init__(self, iter):
self.source = iter
self.stored = False
def __iter__(self):
return self
def __nonzero__(self):
if self.stored:
return True
try:
self.value = next(self.source)
self.stored = True
except StopIteration:
return False
return True
def __next__(self): # use "next" (without underscores) for Python 2.x
if self.stored:
self.stored = False
return self.value
return next(self.source)
使用方法如下:
with open(filename, 'r') as f:
f = GenWrapper(f)
if f:
print 'Not empty'
else:
print 'Empty'
请注意,您可以随时检查是否为空,而不仅仅是在迭代开始时。
在马克·兰瑟姆(Mark Ransom)的提示下,这是一个可用于包装任何迭代器的类,以便您可以窥视,将值推回到流中并检查是否为空。这是一个简单的想法,具有一个简单的实现,过去我很方便。
class Pushable:
def __init__(self, iter):
self.source = iter
self.stored = []
def __iter__(self):
return self
def __bool__(self):
if self.stored:
return True
try:
self.stored.append(next(self.source))
except StopIteration:
return False
return True
def push(self, value):
self.stored.append(value)
def peek(self):
if self.stored:
return self.stored[-1]
value = next(self.source)
self.stored.append(value)
return value
def __next__(self):
if self.stored:
return self.stored.pop()
return next(self.source)
刚好落在这个线程上,并意识到缺少一个非常简单易读的答案:
def is_empty(generator):
for item in generator:
return False
return True
如果我们不打算消耗任何物品,那么我们需要将第一个物品重新注入到生成器中:
def is_empty_no_side_effects(generator):
try:
item = next(generator)
def my_generator():
yield item
yield from generator
return my_generator(), False
except StopIteration:
return (_ for _ in []), True
例:
>>> g=(i for i in [])
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
True
>>> g=(i for i in range(10))
>>> g,empty=is_empty_no_side_effects(g)
>>> empty
False
>>> list(g)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> gen = (i for i in [])
>>> next(gen)
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
next(gen)
StopIteration
在生成器结尾处StopIteration
引发,因为在您的情况下立即到达结尾,所以引发异常。但通常您不应该检查是否存在下一个值。
您可以做的另一件事是:
>>> gen = (i for i in [])
>>> if not list(gen):
print('empty generator')
如果您在使用发电机之前需要知道,那么没有,没有简单的方法。如果您可以等到使用发电机后再使用,则有一种简单的方法:
was_empty = True
for some_item in some_generator:
was_empty = False
do_something_with(some_item)
if was_empty:
handle_already_empty_generator_case()
只需用itertools.chain包装生成器,然后将表示可迭代结束的内容作为第二个可迭代,然后进行简单检查即可。
例如:
import itertools
g = some_iterable
eog = object()
wrap_g = itertools.chain(g, [eog])
现在剩下的就是检查我们附加到iterable末尾的值,当您读取它时,它将表示末尾
for value in wrap_g:
if value == eog: # DING DING! We just found the last element of the iterable
pass # Do something
eog = object()
而不是假设它float('-inf')
永远不会在迭代中发生。
在我的情况下,我需要先了解是否填充了许多生成器,然后再将其传递给一个函数,该函数合并了各个项,即zip(...)
。解决方案与接受的答案相似但足够不同:
定义:
def has_items(iterable):
try:
return True, itertools.chain([next(iterable)], iterable)
except StopIteration:
return False, []
用法:
def filter_empty(iterables):
for iterable in iterables:
itr_has_items, iterable = has_items(iterable)
if itr_has_items:
yield iterable
def merge_iterables(iterables):
populated_iterables = filter_empty(iterables)
for items in zip(*populated_iterables):
# Use items for each "slice"
我的特定问题具有以下属性:可迭代项为空或具有完全相同的条目数。
我发现只有这种解决方案也可以用于空迭代。
def is_generator_empty(generator):
a, b = itertools.tee(generator)
try:
next(a)
except StopIteration:
return True, b
return False, b
is_empty, generator = is_generator_empty(generator)
或者,如果您不想为此使用异常,请尝试使用
def is_generator_empty(generator):
a, b = itertools.tee(generator)
for item in a:
return False, b
return True, b
is_empty, generator = is_generator_empty(generator)
在标记的解决方案中,您无法将其用于空发生器,例如
def get_empty_generator():
while False:
yield None
generator = get_empty_generator()
这是一个包装生成器的简单装饰器,因此如果为空,则返回None。如果您的代码需要在循环遍历之前知道生成器是否会生成任何东西,这将很有用。
def generator_or_none(func):
"""Wrap a generator function, returning None if it's empty. """
def inner(*args, **kwargs):
# peek at the first item; return None if it doesn't exist
try:
next(func(*args, **kwargs))
except StopIteration:
return None
# return original generator otherwise first item will be missing
return func(*args, **kwargs)
return inner
用法:
import random
@generator_or_none
def random_length_generator():
for i in range(random.randint(0, 10)):
yield i
gen = random_length_generator()
if gen is None:
print('Generator is empty')
其中一个有用的示例是在模板代码中-即jinja2
{% if content_generator %}
<section>
<h4>Section title</h4>
{% for item in content_generator %}
{{ item }}
{% endfor %
</section>
{% endif %}
怎么样使用any()?我将其与发电机配合使用,并且工作正常。这里有人解释一下
any(generator)
当您知道生成器将生成可以强制转换为的值时,此方法就起作用了bool
-基本数据类型(例如,int,string)起作用。any(generator)
当生成器为空时,或者当生成器仅具有假值时,它将为False-例如,如果生成器将生成0,''(空字符串)和False,则它仍将为False。只要您知道,这可能是或可能不是预期的行为:)
在cytoolz中使用偷看功能。
from cytoolz import peek
from typing import Tuple, Iterable
def is_empty_iterator(g: Iterable) -> Tuple[Iterable, bool]:
try:
_, g = peek(g)
return g, False
except StopIteration:
return g, True
此函数返回的迭代器将等效于作为参数传入的原始迭代器。
我通过使用sum函数解决了它。请参阅下面的示例,我使用了glob.iglob(它返回一个生成器)。
def isEmpty():
files = glob.iglob(search)
if sum(1 for _ in files):
return True
return False
*这可能不适用于巨大的生成器,但对于较小的列表应该表现良好