空の反復性をループできるようにするための行動を起こす慣用的な方法
-
28-09-2019 - |
質問
反復性をループしており、イテレーターが空である場合は何らかのアクションを実行したいとします。これを行うために考えることができる2つの最良の方法は、次のとおりです。
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
1つ目は、コレクションであることに依存します(ループがある関数/メソッドに反復可能に渡された場合には役に立たない)および2番目のセット empty
醜いように見えるループを通過するたびに。
私が欠けている別の方法はありますか、それとも2番目の代替品が最高ですか?私がこれを私のために処理するループステートメントに追加できる条項がある場合、それは本当にクールでしょう else
作る not_found
旗が消えます。
私は賢いハックを探していません。
私は多くのコードを含むソリューションを探していません
シンプルな言語機能を探しています。私は探しています クリア と Pythonic 反復可能な反復を繰り返し、経験豊富なPythonプログラマーが理解できるという反復性が空である場合に何らかのアクションを実行します。すべての反復にフラグを設定せずにそれを行うことができれば、それは素晴らしいでしょう。これを行う単純なイディオムがない場合は、それを忘れてください。
解決
これはこれを行う最もきれいな方法だと思います:
# 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
そして、ループの後に存在するかどうかを確認します(そうでない場合、ループは決して起こりませんでした):
try:
del i
except NameException: pass
for i in iterable:
do_something(i)
try:
del i
except NameException:
do_something_else()
旗を使うよりもそれはおそらく醜いと思います
更新2
好きだった Odomontoisの答え. 。 IMHOこれは、私が以下に書いたものよりもこの問題により適しています。
アップデート
(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()
if not map(do_something_callable,iterable) :
# do something else
消費される前にイテレーターを部分的にチェックする場合の一般的な方法は使用することです itertools.tee
. 。このようにして、最初から他のコピーを消費しながら、イテレーターの2つのコピーを繰り返して、1つを空虚に確認できます。
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。
「if」と「for」を逆転させるのはどうですか:
if iterable:
for i in iterable:
do_something(i)
else:
do_something_else()
わかりました、これはうまくいきません!
他の解決策は次のとおりです。 http://code.activestate.com/recipes/413614-testing-for-an-empt--iterator/
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"
ジェネレーターには、ジェネレーターが使い果たされるとはいませんが、停止が発生した後にのみ「gi_frame」プロパティがあります。それが受け入れられるなら、ここにあなたが試すことができるものがあります:
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)