私はPythonを学び始めており、その中にyieldステートメントを含むジェネレーター関数に出会いました。これらの関数が解決に本当に優れている問題の種類を知りたいです。
ジェネレーターは遅延評価を提供します。明示的に 'for'で反復するか、反復する関数または構造に渡すことで暗黙的に反復して使用します。ジェネレーターは、リストを返すかのように複数のアイテムを返すと考えることができますが、一度にすべてを返すのではなく、次々にアイテムが要求されるまでジェネレーター関数が一時停止します。
ジェネレーターは、すべての結果が必要かどうかわからない場合や、すべての結果に同時にメモリを割り当てたくない場合に、結果の大きなセット(特にループ自体を含む計算)を計算するのに適しています。または、ジェネレーターがanotherジェネレーターを使用するか、他のリソースを消費する状況では、それが可能な限り遅く発生した方が便利です。
ジェネレーターのもう1つの使用法(実際は同じです)は、コールバックを反復で置き換えることです。状況によっては、関数に多くの作業を行わせ、場合によっては呼び出し元にレポートを返す必要があります。従来、これにはコールバック関数を使用していました。このコールバックを仕事関数に渡すと、このコールバックが定期的に呼び出されます。ジェネレーターのアプローチは、仕事関数(現在はジェネレーター)がコールバックについて何も知らず、何かを報告したいときはいつでも譲ることです。呼び出し元は、別個のコールバックを作成してそれを仕事関数に渡す代わりに、ジェネレーターの周りの小さな「for」ループですべてのレポート作業を行います。
たとえば、「ファイルシステム検索」プログラムを作成したとします。検索全体を実行し、結果を収集して、一度に1つずつ表示できます。最初の結果を表示する前に、すべての結果を収集する必要があり、すべての結果が同時にメモリに保存されます。または、検索中に結果を表示することもできます。これにより、メモリ効率が向上し、ユーザーにとって使いやすくなります。後者は、結果出力関数をファイルシステム検索関数に渡すことで実行できます。または、検索関数をジェネレーターにして、結果を反復処理するだけで実行できます。
後者の2つのアプローチの例をご覧になりたい場合は、os.path.walk()(コールバック付きの古いファイルシステムウォーク関数)およびos.walk()(新しいファイルシステムウォークジェネレータ)をご覧ください。もちろん、すべての結果をリストで収集したい場合、ジェネレーターアプローチはビッグリストアプローチに変換するのは簡単です。
big_list = list(the_generator)
ジェネレーターを使用する理由の1つは、ある種のソリューションに対してソリューションを明確にすることです。
もう1つは、結果を一度に1つずつ処理し、処理する結果の巨大なリストを作成することを避けます。
次のようなfibonacci-up-to-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つの値のみが作成されます。リストが最初に作成されるリストバージョンを使用する場合は、そうではありません。
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()
Pythonのreturn
ステートメントとyield
ステートメントの違いはこれです。
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 - 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-What are they?からフィボナッチの例を入手しましたが、少し想像してみれば、ジェネレーターがfor
ループやその他の従来の反復構造の優れた代替手段となるその他の多くの状況。
簡単な説明:for
ステートメントを考えます
for item in iterable:
do_stuff()
多くの場合、iterable
のすべてのアイテムは最初からそこにある必要はありませんが、必要に応じてその場で生成できます。これは両方でより効率的です
また、事前にすべてのアイテムを知らない場合もあります。例えば:
for command in user_input():
do_stuff_with(command)
事前にすべてのユーザーのコマンドを知る方法はありませんが、コマンドを処理するジェネレーターがある場合、次のようなNiceループを使用できます。
def user_input():
while True:
wait_for_command()
cmd = get_command()
yield cmd
ジェネレーターを使用すると、無限シーケンスを反復することもできます。これは、コンテナーを反復する場合はもちろん不可能です。
私のお気に入りの用途は、「フィルター」操作と「削減」操作です。
ファイルを読んでいて、「##」で始まる行だけが必要だとしましょう。
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つの値だけ別のシーケンスを生成できます。
ジェネレータを使用できる実用的な例は、何らかの形状があり、そのコーナー、エッジなどを反復処理する場合です。私自身のプロジェクト(ソースコード ここ )には、長方形がありました:
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()
で呼び出します。 for
式でクラスインスタンス名を直接使用できるので、__iter__
を使用する方がよりエレガントです。
ここでいくつかの良い答えがありますが、ジェネレーターのより強力なユースケースのいくつかを説明するのに役立つPython Functional Programming tutorial を完全に読むこともお勧めします。
Webサーバーがプロキシとして機能している場合、ジェネレーターを使用します。
ジェネレーターの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
これらは非常に簡単な例ですが、大規模な(潜在的に無限大!)データセットを事前に生成せずに処理するのにどのように役立つかを見ることができます。
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)