Python空のジェネレーター関数


97

Pythonでは、次のように関数の本文にyieldキーワードを置くことで、イテレータ関数を簡単に定義できます。

def gen():
    for i in range(100):
        yield i

値を生成しない(0値を生成する)ジェネレーター関数を定義するには、次のコードは機能しません。Pythonは、それがジェネレーターであり、通常の関数ではないことを認識できないためです。

def empty():
    pass

私は次のようなことができました

def empty():
    if False:
        yield None

しかし、それは非常に醜いでしょう。空のイテレータ関数を実現する良い方法はありますか?

回答:


132

returnジェネレーターで1回使用できます。何も生成せずに反復を停止するため、関数をスコープ外で実行させる明示的な代替手段を提供します。したがって、を使用yieldして関数をジェネレーターに変換returnしますが、何かを生成する前にジェネレーターを終了する前に使用します。

>>> def f():
...     return
...     yield
... 
>>> list(f())
[]

私はそれがあなたが持っているものよりはるかに優れているとは確信していません-それは単に何もしないifステートメントを何もしないステートメントに置き換えるだけyieldです。しかし、それはもっと慣用的です。使用するだけでyieldは機能しないことに注意してください。

>>> def f():
...     yield
... 
>>> list(f())
[None]

なぜ使用しないのiter(())ですか?

この質問は、特に空のジェネレータ関数について尋ねます。そのため、私は一般的に空のイテレータを作成する最良の方法についての質問ではなく、Pythonの構文の内部的な一貫性についての質問だと考えています。

質問が実際に空のイテレータを作成する最良の方法に関するものである場合、代わりに使用することについてZectbumoに同意することができますiter(())。ただし、それiter(())が関数を返さないことを確認することが重要です!空のイテラブルを直接返します。イテラブルを返す callableを期待するAPIを使用しているとします。次のようなことをする必要があります:

def empty():
    return iter(())

(クレジットは、この回答の最初の正しいバージョンを提供するためにUnutbuに行く必要があります。)

さて、あなたは上記がより明確になるかもしれませんが、それがそれほど明確ではない状況を想像することができます。(考案された)ジェネレーター関数定義の長いリストの次の例を考えます。

def zeros():
    while True:
        yield 0

def ones():
    while True:
        yield 1

...

その長いリストの最後にはyield、次のようにaが含まれているものが見たいです。

def empty():
    return
    yield

または、Python 3.3以降(DSMで推奨)では、次のようになります。

def empty():
    yield from ()

yieldキーワードの存在により、これが他のすべての関数とまったく同じように、単なる別のジェネレーター関数であることが一目でわかります。iter(())バージョンが同じことを行っていることを確認するには、もう少し時間がかかります。

それは微妙な違いですが、私は正直に言って- yieldベースの関数の方が読みやすく、保守しやすいと思います。


これは確かに優れてif False: yieldいますが、このパターンを知らない人にとってはやや混乱しています
Konstantin Weitz

1
ええと、帰った後の何か?のようなものを期待していましたitertools.empty()
Grault、2014年

1
@Jesdiscipleは、まあ、returnジェネレータの中で何か違うことを意味します。それはもっと似ていbreakます。
sendle 2014年

(比較的)簡潔であり、と比較するような追加の作業を行わないため、このソリューションが好きFalseです。
Pi Marillion、2016年

Unutbuの答えはイテレータを返すので、あなたが言ったように「真のジェネレータ関数」ではありません。
Zectbumo 2017年

72
iter(())

ジェネレータは必要ありません。おいおい!


3
私は間違いなくこの答えが一番好きです。定数iter([])()あるという単純な事実[]が呼び出されるたびにメモリ内でインスタンス化される可能性があるという単純な事実よりも、すばやく簡単に記述でき、実行が速く、魅力的です。
Mumbleskates

私にとっても、これは最もエレガントな解決策のようです。
Matthias CM Troffaes 2015

2
このスレッドを振り返ると、ジェネレーター関数の真のドロップイン置換が必要なempty = lambda: iter(())場合は、またはのようなものを書く必要があることを指摘せざるを得ませんdef empty(): return iter(())
センダーレ2017

ジェネレータが必要な場合は、他の人が示唆しているように(_ for _ in())を使用することもできます
Zectbumo

1
@Zectbumo、それはまだジェネレーター関数ではありません。それは単なるジェネレーターです。ジェネレータ関数は、呼び出されるたびに新しいジェネレータを返します。
センダーレ

50

Python 3.3(私はyield fromキックしているため、@ senderleが私の最初の考えを盗んだため):

>>> def f():
...     yield from ()
... 
>>> list(f())
[]

しかし、私は認めざるを得ない、私はこのためのユースケースを考え出す苦労してるiter([])(x)range(0)も同様に動作しません。


私はこの構文が本当に好きです。からの収量は素晴らしいです!
Konstantin Weitz

2
これはどちらreturn; yieldかよりも初心者にとってはるかに読みやすいと思いますif False: yield None
abarnert 2014

1
これは最もエレガントなソリューションです
Maksym Ganenko '18 / 10/18

「しかし、認めざるを得ない。このための、iter([])または(x)range(0)同じようにうまく機能しないユースケースを考え出すのに苦労している」->何であるか(x)range(0)はわかりませんが、一部の継承クラスでは、完全なジェネレーターでオーバーライドすることを目的としたメソッドをユースケースにすることができます。一貫性を保つために、他のユーザーが継承するベースジェネレーターでも、オーバーライドするジェネレーターと同じようにジェネレーターを返す必要があります。
VedranŠego19年


4

それはジェネレータ関数でなければなりませんか?そうでない場合はどうですか

def f():
    return iter([])

3

空のイテレータを作成する「標準」の方法はiter([])のようです。[]をiter()のデフォルト引数にすることを提案しました。これは良い議論で拒否されました 。http //bugs.python.org/issue25215を参照してください-Jurjen


1

@senderleが言ったように、これを使用してください:

def empty():
    return
    yield

私は主にこの答えを書いて、それに対する別の正当化を共有します。

他のソリューションよりもこのソリューションを選択する理由の1つは、インタープリターに関する限り最適であるということです。

>>> import dis
>>> def empty_yield_from():
...     yield from ()
... 
>>> def empty_iter():
...     return iter(())
... 
>>> def empty_return():
...     return
...     yield
...
>>> def noop():
...     pass
...
>>> dis.dis(empty_yield_from)
  2           0 LOAD_CONST               1 (())
              2 GET_YIELD_FROM_ITER
              4 LOAD_CONST               0 (None)
              6 YIELD_FROM
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
>>> dis.dis(empty_iter)
  2           0 LOAD_GLOBAL              0 (iter)
              2 LOAD_CONST               1 (())
              4 CALL_FUNCTION            1
              6 RETURN_VALUE
>>> dis.dis(empty_return)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE
>>> dis.dis(noop)
  2           0 LOAD_CONST               0 (None)
              2 RETURN_VALUE

ご覧のとおり、 empty_returnとおり、のバイトコードは通常の空の関数とまったく同じです。残りは、動作を変更しない他の多くの操作を実行します。唯一の違いempty_returnとは、noop前者が発生フラグが設定されていることです。

>>> dis.show_code(noop)
Name:              noop
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
   0: None
>>> dis.show_code(empty_return)
Name:              empty_return
Filename:          <stdin>
Argument count:    0
Positional-only arguments: 0
Kw-only arguments: 0
Number of locals:  0
Stack size:        1
Flags:             OPTIMIZED, NEWLOCALS, GENERATOR, NOFREE
Constants:
   0: None

もちろん、この議論の強さは、使用中のPythonの特定の実装に大きく依存しています。十分に賢い代替インタープリターは、他の操作が何の役にも立たないことに気づき、それらを最適化します。ただし、そのような最適化が存在する場合でもiter、グローバルスコープの識別子が他の何かにリバインドされるなど、最適化の仮定が崩れるのを防ぐために、インタプリタはそれらの実行に時間を費やす必要があります(それがバグを示す可能性が最も高い場合でも)実際に起こりました)。の場合empty_return単に最適化するものが何もない、比較的ナイーブなCPythonでさえ、誤った操作に時間を浪費することはありません。


0
generator = (item for item in [])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.