Python で「yield」キーワードは何をするのですか? 質問する

Python で「yield」キーワードは何をするのですか? 質問する

yieldPython のキーワードはどのような機能を提供しますか?

たとえば、私はこのコード1を理解しようとしています:

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

発信者は次のとおりです。

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

メソッド_get_child_candidatesが呼び出されると何が起こりますか? リストが返されますか? 単一の要素ですか? 再度呼び出されますか? 後続の呼び出しはいつ停止しますか?


1. このコードは、メトリック空間用の優れた Python ライブラリを作成した Jochen Schulz (jrschulz) によって作成されました。完全なソースへのリンクは次のとおりです: Module mspace

ベストアンサー1

何が起こっているかを理解するには、ジェネレーター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 回しか使用できないため、2 回目は実行できませんfor i in mygenerator。ジェネレーターは 0 を計算し、次にそれを忘れて 1 を計算し、4 を 1 つずつ計算して終了します。

収率

yieldは のように使用されるキーワードですreturnが、関数はジェネレータを返します。

>>> def create_generator():
...    mylist = range(3)
...    for i in mylist:
...        yield i*i
...
>>> mygenerator = create_generator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in mygenerator:
...     print(i)
0
1
4

これは役に立たない例ですが、関数が 1 回だけ読み取る必要のある膨大な値のセットを返すことが分かっている場合は便利です。

をマスターするには、関数を呼び出したときに、関数本体に記述したコードは実行されないこと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 are 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 the distance is ok, then you can fill in the result
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)

    # Add the children of the candidate to the candidate's list
    # so the loop will keep running until it has 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]

しかし、あなたのコードではジェネレーターが取得されます。これは次の理由で良いことです:

  1. 値を2回読み取る必要はありません。
  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は、こちらの記事で詳しく説明しています

おすすめ記事