[Python Home] [PEP Index] [PEP Source] |
PEP: | 289 |
---|---|
Title: | Generator Expressions |
Version: | $Revision: 1.11 $ |
Last-Modified: | $Date: 2003/10/28 18:06:32 $ |
Author: | python@rcn.com (Raymond D. Hettinger) |
Status: | Accepted |
Type: | Standards Track |
Content-Type: | text/x-rst |
Created: | 30-Jan-2002 |
Python-Version: | 2.3 |
Post-History: | 22-Oct-2003 |
X-Translator: | Matsui Fe2+ Tetsushi |
リスト内包表現は Python に広範囲に使われるようになった。 しかし、多くのユースケースではメモリー上にリスト全体を作る必要はない。 その代わりに必要なのは一度に一つずつの要素を取り出すことだ。
たとえば以下の和を求めるコードは、平方数のリストをメモリー上に作り上げ、それらの値を一つずつ使い、参照がもはや必要なくなったらリストを削除する:
sum([x*x for x in range(10)])
代わりにジェネレーター表現を使うことによって時間と明確さとメモリーが保全される:
sum(x*x for x in range(10))
同様な利益をコンテナ・オブジェクトのコンストラクターにも見出せる:
s = Set(word for line in page for word in line.split()) d = dict( (k, func(k)) for k in keylist)
ジェネレーター表現は sum(), min(), max() など繰り返し可能な入力を一つの値に簡約する関数に殊に有用である:
max(len(line) for line in file if line.strip())
ジェネレーター表現はまたラムダ式を使った汎関数の使用例の代わりにも使える:
reduce(lambda s, a: s + a.myattr, data, 0) reduce(lambda s, a: s + a[3], data, 0)
これらは簡単に以下のようになる:
sum(a.myattr for a in data) sum(a[3] for a in data)
リスト内包表現は filter() や map() の必要性を大幅に減らした。 同じようにジェネレーター表現は itertools.ifilter() や itertools.imap() の必要性を最小化すると期待される。 その反対に、その他の itertools の有用性はジェネレーター表現によって強化される:
dotproduct = sum(x*y for x,y in itertools.izip(x_vector, y_vector))
リスト内包表現と類似した文法を持つことはアプリケーションをスケールアップする際に既存のコードをジェネレーター表現に変換することを容易にする。
この PEP は Py2.4 に向けて受け入れられた。
(ここにあるものはどれも火星からの読者の目に十分に正確ではないだろうが、例が c.l.py での議論に十分な意図を伝えることを願う。 Python ライブラリー・リファレンスには 100% 正確な意味論と文法の仕様が書いてあるべきだ。)
ジェネレーター表現は無名のジェネレーターを生成しそれを呼ぶことと同じ意味を持つ。 たとえば:
g = (x**2 for x in range(10)) print g.next()
は以下と同値:
def __gen(): for x in range(10): yield x**2 g = __gen() print g.next()
文法上ジェネレーター表現は常に一組の括弧のすぐ内側になければならず、左右どちら側にもコンマがあってはならない。 CVS の Grammar/Grammar でいうならば二つの規則が変更される:
規則:
atom: '(' [testlist] ')'
は以下のように変更される:
atom: '(' [listmaker1] ')'
ここで listmaker1 は listmaker とほぼ同じだが、一つのテストのみが 'for' ... 'in' の後に許される。
arglist に対する規則も同様の変更が必要である。
つまりこう書くことができる:
sum(x**2 for x in range(10))
しかしこう書かなければならない:
reduce(operator.add, (x**2 for x in range(10)))
そしてまたこうである:
g = (x**2 for x in range(10))
すなわち、関数が一つだけ位置で指定される引数を持つのであれば余計な括弧を用いずにジェネレーター表現を書けるが、それ以外のすべての場合には括弧付けしなければならない。
ループ変数は(それが単純な変数または単純な変数のタプルならば)それを取り囲む関数には晒されない。 これにより実装が容易になり、典型的なユースケースはより信頼できるものとなる。 Python の将来のバージョンにおいて、リスト内包表現も取り囲むコードから帰納変数を隠すようになる(Py2.4 では帰納変数にアクセスするコードに警告が出される)。
たとえば:
x = "hello" y = list(x for x in "abc") print x # "hello"と出力, "c"ではない
自由変数の束縛は関数定義時に取り込まれ、デフォルト引数の値を使って送り込まれる。 たとえば:
x = 0 g = (x for c in "abc") # xはループ変数ではない! x = 1 print g.next() # 0(取り込まれたx)を出力, 1(現在のx)ではない
この自由変数に関する振舞いはジェネレーター表現が定義後に評価される時点でのほとんど常に想定される振舞いである。 実際、今まで、定義時の自由変数の値ではなく実行時の値を使う方が良いようなコード例は知られていない。
注意して欲しいのは自由変数はコピーされるわけではなく、単に束縛が取り込まれるだけだということである。 それらがもし変更可能なものであれば変更され得る。 たとえば:
x = [] g = (x for c in "abc") x.append(1) print g.next() # [1]を出力, []ではない
リスト内包表現は変更されずに残る。 たとえば:
[x for x in S] # これはリスト内包表現 [(x for x in S)] # これはジェネレーター表現を含んだ式
残念ながら、今のところ少しだけ文法的な違いがある。 式:
[x for x in 1, 2, 3]
は正しく、意味としては以下と同じ:
[x for x in (1, 2, 3)]
しかしジェネレーター表現は前者のような書き方を許さない:
(x for x in 1, 2, 3)
は不正である。
前者のリスト内包表現の文法は Python 3.0 では不正となる予定であり、Python 2.4 以降、非推奨となるべきである。
リスト内包表現はループ変数を取り囲むスコープに "漏らす"。 これも Python 3.0 では変更され、リスト内包表現の意味定義は Python 3.0 では list(<ジェネレーター表現>) と同値になる。 Python 2.4 以降、リスト内包表現のループ変数がすぐ外側のスコープで使われている名前だった場合に非推奨の警告が出されるべきである。
ジェネレーター表現の使い勝手は sum() や min() や max() などの簡約関数と組み合わせると大いに向上する。 独立した提案が出される予定だが、そこではいくつかの新しい積算関数が推薦される。 次のものを含む可能性が高い: product(), average(), alltrue(), anytrue(), nlargest(), nsmallest()。
[1] | PEP 202 List Comprehensions http://www.python.org/peps/pep-0202.html |
[2] | PEP 255 Simple Generators http://www.python.org/peps/pep-0255.html |
[3] | Peter Norvig の積算表示の提案 http://www.norvig.com/pyacc.html |
[4] | Jeff Epler は以前提案されたブラケットと yield の文法のデモパッチを作った http://python.org/sf/795947 |
This document has been placed in the public domain.