1. ホーム
  2. python

リスト内包はループ変数に書き込むが、ジェネレータは書き込まないのはなぜか?[重複しています]。

2023-10-22 15:06:08

質問

リスト内包で何かすると、ローカル変数に書き込まれます。

i = 0
test = any([i == 2 for i in xrange(10)])
print i

これは "9" と表示されます。しかし、ジェネレータを使用すると、ローカル変数に書き込まれません。

i = 0
test = any(i == 2 for i in xrange(10))
print i

これは "0"と表示されます。

この違いには何か良い理由があるのでしょうか。これは設計上の決定なのでしょうか、それともジェネレータとリスト内包が実装されている方法による単なるランダムな副産物なのでしょうか。個人的には、リスト内包がローカル変数に書き込まない方が良いように思います。

どのように解決するのですか?

Pythonの生みの親であるGuido van Rossumは、このことについて書いたときに言及しています。 ジェネレータ式 は Python 3 に統一的に組み込まれました: (強調)

Python 3 では、リスト内包とジェネレーター式の等価性を向上させるために、もう 1 つの変更も行いました。Python 2 では、リスト内包はループ制御変数を周囲のスコープに "leaks" します。

x = 'before'
a = [x for x in 1, 2, 3]
print x # this prints '3', not 'before'

これはリスト内包のオリジナルの実装の産物です。これは何年もの間、Pythonの"汚い小さな秘密"の1つでした。これは、リスト内包を圧倒的に速くするための意図的な妥協として始まり、初心者によくある落とし穴ではありませんでしたが、間違いなく時々人を刺します。 ジェネレータ式では、私たちはこれを行うことができませんでした。ジェネレータ式はジェネレータを使用して実装され、その実行には別の実行フレームが必要です。したがって、ジェネレータ式は (特に短いシーケンスを反復する場合)、リスト内包よりも効率が悪かったのです。

しかし、Python 3 では、ジェネレーター式と同じ実装戦略を使用することで、リスト内包の "dirty little secret" を修正することにしました。したがって、Python 3 では、上記の例 (print(x) を使用するように変更後 :-) は 'before' を表示し、リスト内包の 'x' が周囲のスコープの 'x' を一時的にシャドウするがオーバーライドしないことを証明します。

というわけで、Python 3ではもうこのようなことは起こらないでしょう。

興味深いことに ディクショナリ これは、dict内包がPython 3からバックポートされ、すでにそのような修正がなされていたためです。

このトピックをカバーする他の質問もいくつかありますが、トピックを検索したときに、すでにそれらを見たことがあるのではないでしょうか?)