回答:
ジェネレータは遅延評価を提供します。「for」を使用して明示的に、または反復する任意の関数または構成に渡すことによって暗黙的に、それらを反復することによって使用します。ジェネレーターは、リストを返すかのように複数のアイテムを返すものと考えることができますが、一度にすべてを返すのではなく、1つずつ返すので、ジェネレーター関数は次のアイテムが要求されるまで一時停止されます。
ジェネレータは、すべての結果が必要かどうかわからない、または同時にすべての結果にメモリを割り当てたくない場合に、結果セット(特にループ自体を含む計算)を計算するのに適しています。 。あるいは、ジェネレーターが別のジェネレーターを使用したり、他のリソースを消費したりする状況で、それが可能な限り遅く発生した方が便利です。
ジェネレーターのもう1つの使用法(実際には同じ)は、コールバックを反復に置き換えることです。状況によっては、関数で多くの作業を行い、時々呼び出し元に報告する必要があります。従来は、このためにコールバック関数を使用していました。このコールバックを仕事関数に渡し、定期的にこのコールバックを呼び出します。ジェネレーターのアプローチは、仕事関数(現在はジェネレーター)がコールバックについて何も知らず、何かを報告したいときはいつでも生成するというものです。呼び出し元は、別個のコールバックを記述してそれを仕事関数に渡す代わりに、ジェネレーターの周りの小さな「for」ループですべてのレポート作業を行います。
たとえば、「ファイルシステム検索」プログラムを作成したとします。検索全体を実行し、結果を収集して、一度に1つずつ表示できます。最初の結果を表示する前にすべての結果を収集する必要があり、すべての結果は同時にメモリに保存されます。または、結果を見つけながら表示することもできます。これにより、メモリ効率が向上し、ユーザーにとってより使いやすくなります。後者は、結果を出力する関数をfilesystem-search関数に渡すことで実行できます。または、検索関数をジェネレーターにして結果を反復処理するだけでも実行できます。
後者の2つのアプローチの例を見たい場合は、os.path.walk()(コールバックを備えた古いファイルシステムウォーキング関数)およびos.walk()(新しいファイルシステムウォーキングジェネレーター)を参照してください。もちろん、あなたは本当にすべての結果をリストに集めたかったのですが、ジェネレーターアプローチはビッグリストアプローチに変換するのは簡単です:
big_list = list(the_generator)
yield
とjoin
後のスレッドを手動で起動するのが面倒でない限り、並行して実行されません(標準のライブラリジェネレーターはこれを実行しません。密かにスレッドを起動することは避けられます)。ジェネレーターyield
は、次の値が要求されるまで、それぞれ一時停止します。ジェネレーターがI / Oをラップしている場合、OSはファイルからのデータをプロアクティブにキャッシュする可能性があります。
ジェネレータを使用する理由の1つは、ある種のソリューションに対してソリューションをより明確にすることです。
もう1つは、一度に1つずつ結果を処理することであり、いずれにせよ分離して処理する結果の巨大なリストを構築することを避けます。
次のようなフィボナッチアップn関数がある場合:
# function version
def fibon(n):
a = b = 1
result = []
for i in xrange(n):
result.append(a)
a, b = b, a + b
return result
あなたはこのように関数をより簡単に書くことができます:
# generator version
def fibon(n):
a = b = 1
for i in xrange(n):
yield a
a, b = b, a + b
機能がわかりやすくなりました。そして、あなたがこのような関数を使うならば:
for x in fibon(1000000):
print x,
この例では、ジェネレーターバージョンを使用している場合、1000000アイテムリスト全体はまったく作成されず、一度に1つの値のみが作成されます。これは、リストが最初に作成されるリストバージョンを使用する場合には当てはまりません。
list(fibon(5))
PEP 255の「動機」セクションを参照してください。
ジェネレータの自明ではない使用は割り込み可能な関数を作成することです。これにより、UIを更新したり、スレッドを使用せずに "同時に"(実際はインターリーブして)複数のジョブを実行したりできます。
私の疑いを晴らすこの説明を見つけます。知らない人Generators
も知らない可能性があるのでyield
戻る
returnステートメントでは、すべてのローカル変数が破棄され、結果の値が呼び出し元に返されます(返されます)。後で同じ関数が呼び出された場合、関数は新しい新しい変数のセットを取得します。
産出
しかし、関数を終了するときにローカル変数が破棄されない場合はどうなりますか?これはresume the function
、中断したところからできることを意味します。ここで、の概念generators
が導入され、中断したyield
ところからステートメントが再開さfunction
れます。
def generate_integers(N):
for i in xrange(N):
yield i
In [1]: gen = generate_integers(3)
In [2]: gen
<generator object at 0x8117f90>
In [3]: gen.next()
0
In [4]: gen.next()
1
In [5]: gen.next()
だから違いだreturn
とyield
Pythonで記述が含まれます。
Yieldステートメントは、関数をジェネレーター関数にするものです。
したがって、ジェネレーターはイテレーターを作成するためのシンプルで強力なツールです。これらは通常の関数のように書かれていますが、yield
データを返したいときにはいつでもステートメントを使用します。next()が呼び出されるたびに、ジェネレーターは中断したところから再開します(すべてのデータ値と最後に実行されたステートメントを記憶しています)。
MySQLテーブルに1億のドメインがあり、各ドメインのAlexaランクを更新するとします。
最初に必要なことは、データベースからドメイン名を選択することです。
テーブル名がでdomains
、列名がだとしますdomain
。
これを使用するSELECT domain FROM domains
と、1億行が返され、大量のメモリが消費されます。したがって、サーバーがクラッシュする可能性があります。
したがって、プログラムをバッチで実行することにしました。バッチサイズが1000であるとします。
最初のバッチでは、最初の1000行をクエリし、各ドメインのAlexaランクを確認して、データベース行を更新します。
2番目のバッチでは、次の1000行に取り組みます。3番目のバッチでは、2001年から3000までとなります。
次に、バッチを生成するジェネレーター関数が必要です。
これがジェネレータ関数です:
def ResultGenerator(cursor, batchsize=1000):
while True:
results = cursor.fetchmany(batchsize)
if not results:
break
for result in results:
yield result
ご覧のとおり、関数yield
は結果を保持し続けます。のreturn
代わりにキーワードを使用した場合yield
、関数がreturnに達すると関数全体が終了します。
return - returns only once
yield - returns multiple times
関数がキーワードを使用する場合、yield
それはジェネレータです。
これで、次のように反復できます。
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="domains")
cursor = db.cursor()
cursor.execute("SELECT domain FROM domains")
for result in ResultGenerator(cursor):
doSomethingWith(result)
db.close()
バッファリング。大きなチャンクでデータをフェッチするのが効率的であるが、小さなチャンクでデータを処理する場合、ジェネレーターが役立ちます。
def bufferedFetch():
while True:
buffer = getBigChunkOfData()
# insert some code to break on 'end of data'
for i in buffer:
yield i
上記により、バッファリングと処理を簡単に分離できます。コンシューマ関数は、バッファリングを気にすることなく、1つずつ値を取得できるようになりました。
ジェネレーターは、コードのクリーンアップや、コードをカプセル化およびモジュール化するための非常にユニークな方法を提供するのに非常に役立つことがわかりました。あなたは独自の内部処理に基づいて、絶えず吐き出す値に何かを必要とし、その何かがあなたのコード内のどこからでも呼び出す必要があるときの状況では(だけでなく、ループまたは例えばブロック内)、発電機があるの機能使用する。
抽象的な例は、ループ内に存在しないフィボナッチ数ジェネレータであり、どこから呼び出されても常にシーケンスの次の数を返します。
def fib():
first = 0
second = 1
yield first
yield second
while 1:
next = first + second
yield next
first = second
second = next
fibgen1 = fib()
fibgen2 = fib()
これで、コード内のどこからでも呼び出すことができる2つのフィボナッチ数ジェネレーターオブジェクトが作成されました。これらのオブジェクトは常に、次のように常に大きいフィボナッチ数を順番に返します。
>>> fibgen1.next(); fibgen1.next(); fibgen1.next(); fibgen1.next()
0
1
1
2
>>> fibgen2.next(); fibgen2.next()
0
1
>>> fibgen1.next(); fibgen1.next()
3
5
ジェネレーターのすばらしい点は、オブジェクトを作成するというフープを通過する必要なく状態をカプセル化することです。それらについて考える1つの方法は、それらの内部状態を記憶する「関数」としてです。
Python Generatorsからフィボナッチの例を取得しました-それらは何ですか?少し想像力をかければ、ジェネレーターがfor
ループや他の従来の反復構造の優れた代替手段となる他の多くの状況を思い付くでしょう。
簡単な説明:for
ステートメントを検討する
for item in iterable:
do_stuff()
多くの場合、のすべてのアイテムiterable
は最初から存在する必要はありませんが、必要に応じてオンザフライで生成できます。これは両方ではるかに効率的です
また、すべてのアイテムを事前に把握していない場合もあります。例えば:
for command in user_input():
do_stuff_with(command)
ユーザーのすべてのコマンドを事前に知る方法はありませんが、コマンドを処理するジェネレーターがある場合は、次のような素敵なループを使用できます。
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
ジェネレーターを使用すると、無限シーケンスを反復することもできます。もちろん、コンテナーを反復する場合は不可能です。
itertool
ためのものがあります-を参照してくださいcycles
。
私のお気に入りの用途は、「フィルター」および「削減」操作です。
ファイルを読み込んでいて、「##」で始まる行だけが必要だとします。
def filter2sharps( aSequence ):
for l in aSequence:
if l.startswith("##"):
yield l
次に、ジェネレータ関数を適切なループで使用できます
source= file( ... )
for line in filter2sharps( source.readlines() ):
print line
source.close()
削減の例も同様です。<Location>...</Location>
行のブロックを見つける必要があるファイルがあるとしましょう。[HTMLタグではなく、たまたまタグのように見える行。]
def reduceLocation( aSequence ):
keep= False
block= None
for line in aSequence:
if line.startswith("</Location"):
block.append( line )
yield block
block= None
keep= False
elif line.startsWith("<Location"):
block= [ line ]
keep= True
elif keep:
block.append( line )
else:
pass
if block is not None:
yield block # A partial block, icky
繰り返しますが、このジェネレータを適切なforループで使用できます。
source = file( ... )
for b in reduceLocation( source.readlines() ):
print b
source.close()
これは、ジェネレーター関数を使用してシーケンスをフィルター処理または削減し、一度に1つの値で別のシーケンスを生成するというものです。
fileobj.readlines()
ファイル全体をメモリ内のリストに読み込み、ジェネレータを使用する目的を無効にします。ファイルオブジェクトは既に反復可能であるため、for b in your_generator(fileobject):
代わりに使用できます。これにより、ファイル全体を読み取らないように、ファイルは一度に1行ずつ読み取られます。
ジェネレーターを利用できる実用的な例は、ある種の形状があり、そのコーナー、エッジなどを反復処理したい場合です。私自身のプロジェクト(ソースコードはこちら)には長方形がありました:
class Rect():
def __init__(self, x, y, width, height):
self.l_top = (x, y)
self.r_top = (x+width, y)
self.r_bot = (x+width, y+height)
self.l_bot = (x, y+height)
def __iter__(self):
yield self.l_top
yield self.r_top
yield self.r_bot
yield self.l_bot
これで、長方形を作成し、そのコーナーをループできます。
myrect=Rect(50, 50, 100, 100)
for corner in myrect:
print(corner)
__iter__
メソッドの代わりに、それiter_corners
をで呼び出すこともできますfor corner in myrect.iter_corners()
。式の__iter__
中でクラスインスタンス名を直接使用できるため、使用する方がエレガントfor
です。
ここでいくつかの良い答えがありますが、ジェネレーターのより強力なユースケースのいくつかを説明するのに役立つPython 関数型プログラミングチュートリアルを完全に読むこともお勧めします。
ジェネレータのsendメソッドは言及されていないため、以下に例を示します。
def test():
for i in xrange(5):
val = yield
print(val)
t = test()
# Proceed to 'yield' statement
next(t)
# Send value to yield
t.send(1)
t.send('2')
t.send([3])
実行中のジェネレーターに値を送信する可能性を示しています。以下のビデオのジェネレーターに関するより高度なコース(yield
説明から、並列処理用のジェネレーター、再帰制限のエスケープなど)
ものの山。アイテムのシーケンスを生成したいが、一度にすべてを「具体化」してリストにする必要がない場合。たとえば、素数を返す単純なジェネレーターがあるとします。
def primes():
primes_found = set()
primes_found.add(2)
yield 2
for i in itertools.count(1):
candidate = i * 2 + 1
if not all(candidate % prime for prime in primes_found):
primes_found.add(candidate)
yield candidate
次に、それを使用して後続の素数の積を生成できます。
def prime_products():
primeiter = primes()
prev = primeiter.next()
for prime in primeiter:
yield prime * prev
prev = prime
これらはかなり自明な例ですが、事前に生成せずに大規模な(潜在的に無限の可能性があります)データセットを処理するのにどのように役立つかを理解できます。これは、より明白な用途の1つにすぎません。
nまでの素数を出力するのにも適しています。
def genprime(n=10):
for num in range(3, n+1):
for factor in range(2, num):
if num%factor == 0:
break
else:
yield(num)
for prime_num in genprime(100):
print(prime_num)