何yield
が機能するかを理解するには、ジェネレータとは何かを理解する必要があります。ジェネレータを理解する前に、イテラブルを理解する必要があります。
イテラブル
リストを作成すると、そのアイテムを1つずつ読み取ることができます。アイテムを1つずつ読み取ることを反復と呼びます。
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
mylist
ある反復可能。リスト内包表記を使用すると、リストが作成され、反復可能になります。
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
" for... in...
"で使用できるものはすべて反復可能です。lists
、strings
、ファイル...
これらのイテラブルは、好きなだけ読み取ることができるので便利ですが、すべての値をメモリに格納しますが、これは、多くの値がある場合に必ずしも必要なことではありません。
発電機
ジェネレーターはイテレーターであり、1回しか反復できない一種の反復可能オブジェクトです。ジェネレータはすべての値をメモリに保存するのではなく、その場で値を生成します。
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
の()
代わりに使用した以外はまったく同じです[]
。ただし、ジェネレーターは1回しか使用できないためfor i in mygenerator
、2回実行することはできません。ジェネレーターは0を計算し、それを忘れて1を計算し、4の計算を1つずつ終了します。
産出
yield
return
関数がジェネレータを返すことを除いて、のように使用されるキーワードです。
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
ここでは役に立たない例ですが、関数が一度だけ読み取る必要がある膨大な値のセットを返すことがわかっている場合に便利です。
をマスターするには、関数を呼び出すときに、関数本体に記述したコードが実行されyield
ないことを理解する必要があります。関数はジェネレーターオブジェクトのみを返します。これは少しトリッキーです:-)
その後、for
ジェネレーターを使用するたびに、中断したところからコードが続行されます。
今難しい部分:
for
関数から作成されたジェネレーターオブジェクトを初めて呼び出すと、関数のコードが最初からに到達するまで実行されyield
、その後、ループの最初の値が返されます。その後、後続の各呼び出しは、関数に記述したループの別の反復を実行し、次の値を返します。これは、ジェネレーターが空であると見なされるまで続きます。これは、関数がを押すことなく実行されるときに発生しyield
ます。ループが終了したか、もはやを満たさなくなったことが原因である可能性があります"if/else"
。
コードの説明
発生器:
# Here you create the method of the node object that will return the generator
def _get_child_candidates(self, distance, min_dist, max_dist):
# Here is the code that will be called each time you use the generator object:
# If there is still a child of the node object on its left
# AND if the distance is ok, return the next child
if self._leftchild and distance - max_dist < self._median:
yield self._leftchild
# If there is still a child of the node object on its right
# AND if the distance is ok, return the next child
if self._rightchild and distance + max_dist >= self._median:
yield self._rightchild
# If the function arrives here, the generator will be considered empty
# there is no more than two values: the left and the right children
発信者:
# Create an empty list and a list with the current object reference
result, candidates = list(), [self]
# Loop on candidates (they contain only one element at the beginning)
while candidates:
# Get the last candidate and remove it from the list
node = candidates.pop()
# Get the distance between obj and the candidate
distance = node._get_dist(obj)
# If distance is ok, then you can fill the result
if distance <= max_dist and distance >= min_dist:
result.extend(node._values)
# Add the children of the candidate in the candidate's list
# so the loop will keep running until it will have looked
# at all the children of the children of the children, etc. of the candidate
candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result
このコードには、いくつかのスマートパーツが含まれています。
ループはリストで繰り返されますが、ループが繰り返されている間、リストは拡張されます:-)これは、無限ループになる可能性があるため少し危険でも、これらのネストされたデータすべてを処理する簡潔な方法です。この場合、candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
ジェネレーターのすべての値を使い果たしますがwhile
、同じノードに適用されないため、以前の値とは異なる値を生成する新しいジェネレーターオブジェクトを作成し続けます。
このextend()
メソッドは、反復可能オブジェクトを期待し、その値をリストに追加するリストオブジェクトメソッドです。
通常、リストを渡します:
>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]
しかし、コードではジェネレーターを取得します。
- 値を2回読み取る必要はありません。
- あなたはたくさんの子供を持っているかもしれません、そしてあなたはそれらをすべてメモリに保存したくないでしょう。
また、Pythonはメソッドの引数がリストであるかどうかを気にしないため、機能します。Pythonはイテラブルを想定しているため、文字列、リスト、タプル、ジェネレーターで動作します!これはダックタイピングと呼ばれ、Pythonが優れている理由の1つです。しかし、これは別の話です、別の質問のために...
ここで停止するか、少し読んでジェネレータの高度な使用法を確認できます。
発電機の消耗を制御する
>>> class Bank(): # Let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # When everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # Crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # It's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # The trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # Build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
注: Python 3の場合は、print(corner_street_atm.__next__())
またはprint(next(corner_street_atm))
これは、リソースへのアクセスの制御など、さまざまな場合に役立ちます。
親友のItertools
itertoolsモジュールには、反復可能オブジェクトを操作するための特別な関数が含まれています。ジェネレータを複製したいですか?2つのジェネレーターをチェーンしますか?ネストされたリストの値をワンライナーでグループ化しますか?Map / Zip
別のリストを作成せずに?
その後だけimport itertools
です。
例?4頭の競馬の可能な到着順序を見てみましょう。
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),
(1, 2, 4, 3),
(1, 3, 2, 4),
(1, 3, 4, 2),
(1, 4, 2, 3),
(1, 4, 3, 2),
(2, 1, 3, 4),
(2, 1, 4, 3),
(2, 3, 1, 4),
(2, 3, 4, 1),
(2, 4, 1, 3),
(2, 4, 3, 1),
(3, 1, 2, 4),
(3, 1, 4, 2),
(3, 2, 1, 4),
(3, 2, 4, 1),
(3, 4, 1, 2),
(3, 4, 2, 1),
(4, 1, 2, 3),
(4, 1, 3, 2),
(4, 2, 1, 3),
(4, 2, 3, 1),
(4, 3, 1, 2),
(4, 3, 2, 1)]
反復の内部メカニズムを理解する
反復は、反復可能オブジェクト(__iter__()
メソッドの実装)と反復子(__next__()
メソッドの実装)を意味するプロセスです。イテラブルは、イテレータを取得できるオブジェクトです。イテレータは、イテラブルを反復できるオブジェクトです。
ループのしくみfor
については、この記事で詳しく説明しています。
yield
この答えが示唆するほど魔法ではありません。yield
どこかにステートメントを含む関数を呼び出すと、ジェネレーターオブジェクトが取得されますが、コードは実行されません。その後、ジェネレータからオブジェクトを抽出するたびに、Pythonはyield
ステートメント内になるまで関数内のコードを実行し、その後、一時停止してオブジェクトを配信します。別のオブジェクトを抽出すると、Pythonはの直後から再開され、別のオブジェクトyield
に到達するまで続きますyield
(多くの場合、同じオブジェクトですが、1回繰り返します)。これは、関数が最後まで実行されるまで続き、その時点で、ジェネレータは使い果たされたと見なされます。