尝试循环空可迭代时采取行动的惯用方法

发布于 2024-09-14 20:51:17 字数 756 浏览 12 评论 0原文

假设我正在循环遍历一个可迭代对象,并且希望在迭代器为空时执行一些操作。我能想到的两种最好的方法是:

for i in iterable:
     # do_something
if not iterable:
    # do_something_else

第一个

empty = True
for i in iterable:
    empty = False
    # do_something
if empty:
    # do_something_else

取决于可迭代对象是一个集合(当可迭代对象传递到循环所在的函数/方法中时非常无用),第二个集合empty,这看起来很难看。

我是否缺少另一种方法,或者第二种选择是最好的吗?如果我可以在循环语句中添加一些子句来为我处理这个问题,就像 else 使 not_found 标志消失一样,那就太酷了。


我并不是在寻找聪明的黑客。

我不是在寻找涉及大量代码的解决方案,

而是在寻找简单的语言功能。 我正在寻找一种清晰pythonic方法来迭代可迭代对象,并在可迭代对象为空时采取一些操作,任何有经验的Python程序员都会理解。如果我可以在每次迭代时不设置标志的情况下做到这一点,那就太棒了。 如果没有简单的习语可以做到这一点,那就忘记它吧。

Suppose that I am looping over a iterable and would like to take some action if the iterator is empty. The two best ways that I can think of to do this are:

for i in iterable:
     # do_something
if not iterable:
    # do_something_else

and

empty = True
for i in iterable:
    empty = False
    # do_something
if empty:
    # do_something_else

The first depends on the the iterable being a collection (so useless for when the iterable gets passed into the function/method where the loop is) and the second sets empty on every pass through the loop which seems ugly.

Is there another way that I'm missing or is the second alternative the best? It would be really cool if there was some clause that I could add to the loop statement that would handle this for me much like else makes not_found flags go away.


I am not looking for clever hacks.

I am not looking for solutions that involve a lot of code

I am looking for a simple language feature.
I am looking for a clear and pythonic way to iterate over an iterable and take some action if the iterable is empty that any experienced python programmer will be understand. If I could do it without setting a flag on every iteration, that would be fantastic.
If there is no simple idiom that does this, then forget about it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

想念有你 2024-09-21 20:51:17

我认为这是最干净的方法:

# first try with exceptions
def nonempty( iter ):
    """ returns `iter` if iter is not empty, else raises TypeError """
    try:
        first = next(iter)
    except StopIteration:
        raise TypeError("Emtpy Iterator")
    yield first
    for item in iter:
        yield item


# a version without exceptions. Seems nicer:
def isempty( iter ):
    """ returns `(True, ())` if `iter` if is empty else `(False, iter)`
         Don't use the original iterator! """
    try:
        first = next(iter)
    except StopIteration:
        return True, ()
    else:
        def iterator():
            yield first
            for item in iter:
                yield item
        return False, iterator()



for x in ([],[1]):
    # first version
    try:
        list(nonempty(iter(x))) # trying to consume a empty iterator raises
    except TypeError:
        print x, "is empty"
    else:
        print x, "is not empty"

    # with isempty
    empty, it = isempty(iter(x))
    print x,  "is", ("empty" if empty else "not empty")

I think this the the cleanest way to do this:

# first try with exceptions
def nonempty( iter ):
    """ returns `iter` if iter is not empty, else raises TypeError """
    try:
        first = next(iter)
    except StopIteration:
        raise TypeError("Emtpy Iterator")
    yield first
    for item in iter:
        yield item


# a version without exceptions. Seems nicer:
def isempty( iter ):
    """ returns `(True, ())` if `iter` if is empty else `(False, iter)`
         Don't use the original iterator! """
    try:
        first = next(iter)
    except StopIteration:
        return True, ()
    else:
        def iterator():
            yield first
            for item in iter:
                yield item
        return False, iterator()



for x in ([],[1]):
    # first version
    try:
        list(nonempty(iter(x))) # trying to consume a empty iterator raises
    except TypeError:
        print x, "is empty"
    else:
        print x, "is not empty"

    # with isempty
    empty, it = isempty(iter(x))
    print x,  "is", ("empty" if empty else "not empty")
清风夜微凉 2024-09-21 20:51:17

这是相当黑客的,但是您可以删除 i ,然后检查它在循环后是否存在(如果不存在,则循环从未发生过):

try:
    del i
except NameException: pass

for i in iterable:
    do_something(i)

try:
    del i
except NameException:
    do_something_else()

我认为这可能比仅使用标志更难看

This is quite hackish, but you can delete i and then check if it exists after the loop (if not, the loop never happened):

try:
    del i
except NameException: pass

for i in iterable:
    do_something(i)

try:
    del i
except NameException:
    do_something_else()

I think that's probably uglier than just using a flag though

北座城市 2024-09-21 20:51:17

更新2

我喜欢Odomontois 的回答。恕我直言,它比我下面写的更适合这个问题。

更新

(阅读OP的评论和编辑的问题后)你也可以这样做。参见下文:

def with_divisible(n, a, b, f):
 it = (i for i in xrange(a, b) if not i % n)
 for i in wrapper(it):
  f(i)

>>> with_divisible(1, 1, 1, lambda x: x)
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    with_divisible(1, 1, 1, lambda x: x)
  File "<pyshell#54>", line 3, in with_divisible
    for i in wrapper(it):
  File "<pyshell#46>", line 4, in wrapper
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

>>> with_divisible(7, 1, 21, lambda x: x)
7
14
...Snipped...
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

原始答案

有趣的问题。我做了一些实验并得出以下结论:

class EmptyIterableException(Exception):
    pass

def wrapper(iterable):
    for each in iterable:
        yield each
    raise EmptyIterableException("Empty")

try:
    for each in wrapper(iterable):
        do_something(each)
except EmptyIterableException, e:
    do_something_else()

Update 2

I liked Odomontois' answer. IMHO it is better suited to this problem than what I have written below.

Update

(After reading the OP's comment and edited question) You can do that too. See below:

def with_divisible(n, a, b, f):
 it = (i for i in xrange(a, b) if not i % n)
 for i in wrapper(it):
  f(i)

>>> with_divisible(1, 1, 1, lambda x: x)
Traceback (most recent call last):
  File "<pyshell#55>", line 1, in <module>
    with_divisible(1, 1, 1, lambda x: x)
  File "<pyshell#54>", line 3, in with_divisible
    for i in wrapper(it):
  File "<pyshell#46>", line 4, in wrapper
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

>>> with_divisible(7, 1, 21, lambda x: x)
7
14
...Snipped...
    raise EmptyIterableException("Empty")
EmptyIterableException: Empty

Original Answer

Interesting problem. I did some experiments and came up with the following:

class EmptyIterableException(Exception):
    pass

def wrapper(iterable):
    for each in iterable:
        yield each
    raise EmptyIterableException("Empty")

try:
    for each in wrapper(iterable):
        do_something(each)
except EmptyIterableException, e:
    do_something_else()
孤寂小茶 2024-09-21 20:51:17
if not map(do_something_callable,iterable) : 
    # do something else
if not map(do_something_callable,iterable) : 
    # do something else
生生不灭 2024-09-21 20:51:17

如果要在使用迭代器之前对其进行部分检查,一般方法是使用 itertools.tee。这样我们就可以拥有迭代器的两个副本,并检查一个副本是否为空,同时仍然从一开始就消耗另一个副本。

from itertools import tee
it1, it2 = tee(iterable)
try:
    it1.next()
    for i in it2:
        do_some_action(i) #iterator is not empty
except StopIteration:
    do_empty_action() #iterator is empty

StopIteration 异常必然是调用 it1.next() 的结果,因为循环内部引发的任何 StopIteration 异常都会终止该循环。

编辑:对于那些不喜欢此类异常的人,可以使用islice来设置单步循环:

from itertools import tee, islice
it1, it2 = tee(iterable)
for _ in islice(it1, 1):
    #loop entered if iterator is not empty
    for i in it2:
        do_some_action(i)
    break #if loop entered don't execute the else section
else:
    do_empty_action()

我个人更喜欢第一种风格。 YMMV。

The general way forward if an iterator is to be partially checked before being consumed is to use itertools.tee. This way we can have two copies of the iterator and check one for emptiness while still consuming the other copy from the start.

from itertools import tee
it1, it2 = tee(iterable)
try:
    it1.next()
    for i in it2:
        do_some_action(i) #iterator is not empty
except StopIteration:
    do_empty_action() #iterator is empty

The StopIteration exception is bound to be a result of the call to it1.next(), as any StopIteration exceptions raised froom inside the loop will terminate that loop.

Edit: for those who don't like such exceptions, islice can be used to set up a single step loop:

from itertools import tee, islice
it1, it2 = tee(iterable)
for _ in islice(it1, 1):
    #loop entered if iterator is not empty
    for i in it2:
        do_some_action(i)
    break #if loop entered don't execute the else section
else:
    do_empty_action()

I personally prefer the first style. YMMV.

夜雨飘雪 2024-09-21 20:51:17

反转“if”和“for”怎么样:

if iterable:
    for i in iterable:
        do_something(i)
else:
    do_something_else()

好吧,这不起作用!

这是另一个解决方案:http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

What about reversing "if" and "for":

if iterable:
    for i in iterable:
        do_something(i)
else:
    do_something_else()

OK, this does not work!

Here is an other solution: http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

橘香 2024-09-21 20:51:17

这是 迈克尔·莫罗泽克FM的回答:

def with_divisible(n, a, b, f):
    '''apply f to every integer x such that n divides x and a <= x < b'''
    it = (i for i in xrange(a, b) if not i % n)
    for i in it:
        f(i)
    try: i            # test if `it` was empty
    except NameError: print('do something else')

def g(i):
    print i,

with_divisible( 3, 1, 10, g)   # Prints 3 6 9.
with_divisible(33, 1, 10, g)   # Prints "do something else"

This is a combination of Michael Mrozek's and FM's answers:

def with_divisible(n, a, b, f):
    '''apply f to every integer x such that n divides x and a <= x < b'''
    it = (i for i in xrange(a, b) if not i % n)
    for i in it:
        f(i)
    try: i            # test if `it` was empty
    except NameError: print('do something else')

def g(i):
    print i,

with_divisible( 3, 1, 10, g)   # Prints 3 6 9.
with_divisible(33, 1, 10, g)   # Prints "do something else"
莫相离 2024-09-21 20:51:17

生成器有一个“gi_frame”属性,一旦生成器耗尽,该属性为 None,但仅在 StopIteration 引发后才为 None。如果可以接受,您可以尝试以下方法:

import types

def do(x, f, f_empty):
    if type(x) == types.GeneratorType:
        # generators have a 'gi_frame' property,
        # which is None once the generator is exhausted
        if x.gi_frame:
            # not empty
            return f(x)
        return f_empty(x)
    if x:
        return f(x)
    return f_empty(x)

def nempty(lst):
    print lst, 'not empty'

def empty(lst):
    print 'Twas empty!'

# lists
do([2,3,4], nempty, empty)
do([], nempty, empty)

# generators
do((i for i in range(5)), nempty, empty)
gen = (i for i in range(1))
gen.next()
try:
    gen.next()
except StopIteration:
    pass
do(gen, nempty, empty)

Generators have a 'gi_frame' property which is None once the generator is exhausted, but only after StopIteration has been raised. If that's acceptable, here's something you could try:

import types

def do(x, f, f_empty):
    if type(x) == types.GeneratorType:
        # generators have a 'gi_frame' property,
        # which is None once the generator is exhausted
        if x.gi_frame:
            # not empty
            return f(x)
        return f_empty(x)
    if x:
        return f(x)
    return f_empty(x)

def nempty(lst):
    print lst, 'not empty'

def empty(lst):
    print 'Twas empty!'

# lists
do([2,3,4], nempty, empty)
do([], nempty, empty)

# generators
do((i for i in range(5)), nempty, empty)
gen = (i for i in range(1))
gen.next()
try:
    gen.next()
except StopIteration:
    pass
do(gen, nempty, empty)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文