我怎么知道一个发电机从一开始是空的?
有没有简单的testing方法,如果发电机没有项目,如偷看,hasNext,isEmpty,沿着这些线?
简单的回答你的问题:不,没有简单的方法。 有很多的解决方法。
实际上不应该有一个简单的方法,因为生成器是什么:一种输出一系列值的方法, 而不需要在内存中保存序列 。 所以没有后向遍历。
你可以编写一个has_next函数,或者甚至可以把它作为一个花哨的装饰器的方法,如果你想要的话,把它拍到一个生成器上。
build议:
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.
一个简单的方法是使用next()的可选参数,如果发生器耗尽(或空),则使用该参数。 例如:
iterable = some_generator() _exhausted = object() if next(iterable, _exhausted) == _exhausted: print('generator is empty')
编辑:纠正了mehtunguh的评论中指出的问题。
我讨厌提供第二个解决scheme,特别是我不会使用自己的解决scheme,但是,如果您绝对必须这样做,而不是像其他答案那样使用生成器:
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)
现在我真的不喜欢这个解决scheme,因为我相信这不是如何使用发电机。
恕我直言,最好的办法是避免一个特殊的testing。 大多数情况下,使用发生器是testing:
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)
如果这还不够好,你仍然可以进行明确的testing。 在这一点上, thing
将包含最后生成的值。 如果什么都没有生成,它将是未定义的 – 除非你已经定义了variables。 你可以检查一下thing
的价值,但是这有点不可靠。 相反,只需在块内设置一个标志,然后检查:
if not thing_generated: print "Avast, ye scurvy dog!"
next(generator, None) is not None
或者更换None
但是不pipe你知道什么值都不在你的发电机中。
编辑 :是的,这将跳过生成器中的1项。 然而,通常我会检查一个生成器是否为空,仅用于validation目的,然后不真正使用它。 否则我会做类似的事情:
def foo(self): if next(self.my_generator(), None) is None: raise Exception("Not initiated") for x in self.my_generator(): ...
对不起,明显的做法,但最好的办法是做:
for item in my_generator: print item
现在您在使用时已经检测到发生器是空的。 当然,如果发生器是空的,项目将永远不会显示。
这可能不完全符合你的代码,但这是生成器的习惯用法:迭代,所以也许你可能会稍微改变你的方法,或者根本不使用生成器。
我意识到这个post现在已经5岁了,但是我在find这样做的地道方式的时候发现了这个post,并且没有看到我的解决scheme。 所以对于后人:
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)
当然,正如我相信很多评论家会指出的那样,这种做法很冒险,而且在某些有限的情况下(例如发电机无副作用),它只能起作用。 因人而异。
所有你需要做的,看看发电机是否是空的是尝试获得下一个结果。 当然,如果你还没有准备好使用这个结果,那么你必须把它存储起来,以后再返回。
这是一个包装类,可以添加到现有的迭代器添加一个__nonzero__
testing,所以你可以看到,如果发电机是空的,用一个简单的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 = self.source.next() self.stored = True except StopIteration: return False return True def next(self): if self.stored: self.stored = False return self.value return self.source.next()
以下是你如何使用它:
with open(filename, 'r') as f: f = GenWrapper(f) if f: print 'Not empty' else: print 'Empty'
>>> gen = (i for i in []) >>> next(gen) Traceback (most recent call last): File "<pyshell#43>", line 1, in <module> next(gen) StopIteration
在发生器结束时,会StopIteration
,因为在您的情况下立即达到结果,将引发exception。 但通常你不应该检查下一个值的存在。
你可以做的另一件事是:
>>> 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()
这里是我简单的方法,我用来继续返回一个迭代器,同时检查是否产生了我只是检查循环运行:
n = 0 for key, value in iterator: n+=1 yield key, value if n == 0: print ("nothing found in iterator) break
这是一个包装生成器的简单装饰器,所以如果为空则返回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 %}
简单地用itertools.chain包装生成器,将代表可迭代结束的东西作为第二个迭代器,然后简单地检查一下。
例如:
import itertools g = some_iterable eog = object() wrap_g = itertools.chain(g, [eog])
现在剩下的就是检查我们追加到迭代器末尾的那个值,当你读到它时,那将意味着结束
for value in wrap_g: if value == eog: # DING DING! We just found the last element of the iterable pass # Do something
使用islice你只需要检查到第一次迭代发现它是否是空的。
从itertools导入islice
def isempty(可迭代):
返回列表(islice(iterable,1))== []
在我的情况下,我需要知道是否有大量的发电机被填充之前,我把它传递给一个函数,合并的项目,即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"
我特别的问题是iterables是空的或者具有完全相同数量的条目。
怎么使用任何()? 我用它与发电机,它工作正常。 这里有一个人解释一下这个
我使用sum函数解决了这个问题。 看下面的例子我用glob.iglob(它返回一个生成器)。
def isEmpty(): files = glob.iglob(search) if sum(1 for _ in files): return True return False
*这可能不适用于巨大的发电机,但应该很好地为较小的名单