リスト内包とジェネレータ式における降伏
質問
以下の動作は、私にはむしろ直感に反しているように見えます(Python 3.4)。
>>> [(yield i) for i in range(3)]
<generator object <listcomp> at 0x0245C148>
>>> list([(yield i) for i in range(3)])
[0, 1, 2]
>>> list((yield i) for i in range(3))
[0, None, 1, None, 2, None]
最後の行の中間値は、実は必ずしも
None
であるとは限らず、私たちが
send
であり、以下のジェネレータと等価である(と思う)。
def f():
for i in range(3):
yield (yield i)
この3行が全く機能しないのはおかしいと思います。その
参照
はこう言っています。
yield
は関数定義の中でのみ許されると書かれています (私の読み方が間違っているか、単に古いバージョンからコピーされただけかもしれませんが)。最初の2行は
SyntaxError
を生成しますが、3 行目は生成されません。
また、奇妙なことに
- リスト内包がリストではなくジェネレータを返すのは
- であり、リストに変換されたジェネレータ式と対応するリスト内包が異なる値を含むこと。
どなたかもっと情報を提供していただけないでしょうか。
どのように解決するのですか?
<ブロッククオート
注意
: これは CPython の
yield
を扱う CPython のバグで、 Python 3.8 で修正され、Python 3.7 で非推奨の警告が出されました。参照
Pythonバグレポート
と
新着情報
のエントリ
Python 3.7
と
Python 3.8
.
ジェネレータ式、および set と dict の内包は、(ジェネレータ) 関数オブジェクトにコンパイルされます。Python 3 では、リスト内包も同じ扱いを受けます。これらはすべて、本質的には新しいネストされたスコープです。
ジェネレータ式を分解してみると、これがわかります。
>>> dis.dis(compile("(i for i in range(3))", '', 'exec'))
1 0 LOAD_CONST 0 (<code object <genexpr> at 0x10f7530c0, file "", line 1>)
3 LOAD_CONST 1 ('<genexpr>')
6 MAKE_FUNCTION 0
9 LOAD_NAME 0 (range)
12 LOAD_CONST 2 (3)
15 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
18 GET_ITER
19 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
22 POP_TOP
23 LOAD_CONST 3 (None)
26 RETURN_VALUE
>>> dis.dis(compile("(i for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 11 (to 17)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 POP_TOP
14 JUMP_ABSOLUTE 3
>> 17 LOAD_CONST 0 (None)
20 RETURN_VALUE
上記は、ジェネレータ式がコードオブジェクトにコンパイルされ、関数としてロードされることを示しています(
MAKE_FUNCTION
はコードオブジェクトから関数オブジェクトを生成します)。このとき
.co_consts[0]
を参照すると、式のために生成されたコードオブジェクトを見ることができ、そのコードオブジェクトは
YIELD_VALUE
を使っています。
このように
yield
の表現は、コンパイラがこれらを偽装された関数と見なすので、そのコンテキストで動作します。
これはバグです。
yield
はこれらの式に含まれません。Python の
文法
はそれを許しますが (これがコードがコンパイル可能な理由です)、Python 3.7以前の
yield
式の指定
を使用すると
yield
を使ってもうまくいかないことがわかります。
yield式は ジェネレータ 関数を定義するときにのみ使用され、したがって、関数定義の本文でのみ使用できます。
これは、バグであることが確認されている
問題 10544
. このバグの解決は
yield
と
yield from
は
を上げる
SyntaxError
Python 3.8 では
Python 3.7では
を発生させます。
DeprecationWarning
を発生させ、コードがこの構成を使うのを止めるようにします。Python 2.7.15 以降で
-3
コマンドラインスイッチ
は、Python 3 互換性警告を有効にします。
3.7.0b1 の警告はこのようになります。警告をエラーにすると
SyntaxError
例外が発生します。
>>> [(yield i) for i in range(3)]
<stdin>:1: DeprecationWarning: 'yield' inside list comprehension
<generator object <listcomp> at 0x1092ec7c8>
>>> import warnings
>>> warnings.simplefilter('error')
>>> [(yield i) for i in range(3)]
File "<stdin>", line 1
SyntaxError: 'yield' inside list comprehension
との違いは
yield
がリスト内包であることと
yield
が動作するのは、この2つの式の実装の違いに起因します。Python 3 では、リスト内包は
LIST_APPEND
を呼び出してスタックの先頭をビルド中のリストに追加しますが、ジェネレータ式はその代わりにその値を生成します。で追加すると
(yield <expr>)
を加えることは、単に別の
YIELD_VALUE
オペコードを追加するだけです。
>>> dis.dis(compile("[(yield i) for i in range(3)]", '', 'exec').co_consts[0])
1 0 BUILD_LIST 0
3 LOAD_FAST 0 (.0)
>> 6 FOR_ITER 13 (to 22)
9 STORE_FAST 1 (i)
12 LOAD_FAST 1 (i)
15 YIELD_VALUE
16 LIST_APPEND 2
19 JUMP_ABSOLUTE 6
>> 22 RETURN_VALUE
>>> dis.dis(compile("((yield i) for i in range(3))", '', 'exec').co_consts[0])
1 0 LOAD_FAST 0 (.0)
>> 3 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (i)
9 LOAD_FAST 1 (i)
12 YIELD_VALUE
13 YIELD_VALUE
14 POP_TOP
15 JUMP_ABSOLUTE 3
>> 18 LOAD_CONST 0 (None)
21 RETURN_VALUE
は
YIELD_VALUE
オペコードはそれぞれバイトコードインデックス 15 と 12 にあり、巣の中のカッコウのように余分なものです。つまり、リスト内包型ジェネレータでは、毎回スタックの先頭を生成する降伏が 1 回あります (スタックの先頭を
yield
の戻り値に置き換えます)、そしてジェネレータ式のバリアントでは、 スタックの一番上(整数)を yield してから
また
の戻り値がスタックに格納されます。
yield
となり
None
となる。
では、リスト内包の場合、意図した
list
オブジェクトの出力はまだ返されますが、Python 3 はこれをジェネレータと見なし、返り値は代わりに
StopIteration
例外
として
value
属性と同じです。
>>> from itertools import islice
>>> listgen = [(yield i) for i in range(3)]
>>> list(islice(listgen, 3)) # avoid exhausting the generator
[0, 1, 2]
>>> try:
... next(listgen)
... except StopIteration as si:
... print(si.value)
...
[None, None, None]
これらは
None
オブジェクトの返り値です。
yield
式の戻り値です。
そして、もう一度繰り返しますが、この同じ問題は Python 2 と Python 3 の辞書と集合の理解にも当てはまります; Python 2 では
yield
の戻り値はまだ意図された辞書やセットオブジェクトに追加され、戻り値は
StopIteration
例外に添付されるのではなく、最後に 'yield' されます。
>>> list({(yield k): (yield v) for k, v in {'foo': 'bar', 'spam': 'eggs'}.items()})
['bar', 'foo', 'eggs', 'spam', {None: None}]
>>> list({(yield i) for i in range(3)})
[0, 1, 2, set([None])]
関連
-
[解決済み] リストのリストからフラットなリストを作るには?
-
[解決済み] staticmethodとclassmethodの違いについて
-
[解決済み] リスト内のアイテムのインデックスを検索する
-
[解決済み] Pythonのリストメソッドであるappendとextendの違いは何ですか?
-
[解決済み] 最小限の驚き」と「変更可能なデフォルトの引数
-
[解決済み] Python 3で「1000000000000000 in range(1000000000000001)」はなぜ速いのですか?
-
[解決済み] リスト内包のif/else
-
[解決済み] ジェネレータ式とリスト内包の比較
-
[解決済み] 範囲指定された浮動小数点数のランダムな配列を生成します。
-
[解決済み] データクラスとtyping.NamedTupleの主な使用例
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 前月の日時オブジェクトを返す
-
[解決済み] Pythonの構文に新しいステートメントを追加することはできますか?
-
[解決済み] django.db.migrations.exceptions.InconsistentMigrationHistory
-
[解決済み] Pythonの要素別タプル演算(sumなど
-
[解決済み] データフレームをソートした後にインデックスを更新する
-
[解決済み] DataFrameに日付間の日数カラムを追加する pandas
-
[解決済み] pandasのタイムゾーンに対応したDateTimeIndexを、特定のタイムゾーンに対応したナイーブなタイムスタンプに変換する。
-
[解決済み] Pythonで、ウェブサイトが404か200かを確認するためにurllibをどのように使用しますか?
-
[解決済み] Python 言語を決定するには?
-
[解決済み] 認証プラグイン 'caching_sha2_password' はサポートされていません。