元の質問は次のとおりです。
yieldキーワードはPythonでどのように使用されますか? それは何をしますか?
たとえば、私はこのコードを理解しようとしています(**):
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 = list(), [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メソッドが呼び出されるとどうなりますか? 返されたリスト、いくつかの要素? 彼は再び呼ばれますか? 後続の通話はいつ終了しますか?
**このコードは、計量空間用の優れたPythonライブラリを作成したJochen Schulz(jrschulz)に属します。 ソースへのリンクは次のとおりです。http : //well-adjusted.de/~jrschulz/mspace/
そして、ここに答えがあります:
イテレータ
yieldが何をするかを理解するには、ジェネレーターが何であるかを理解する必要があります。 ジェネレーターの前にはイテレーターがあります。 リストを作成すると、その要素を1つずつ読み取ることができます-これは反復と呼ばれます:
>>> mylist = [1, 2, 3] >>> for i in mylist : ... print(i) 1 2 3
マイリストは反復可能なオブジェクトです。 ジェネレータ式を使用してリストを作成するとき、イテレータも作成します。
>>> mylist = [x*x for x in range(3)] >>> for i in mylist : ... print(i) 0 1 4
「for ... in ...」コンストラクトに適用できるものはすべて、反復可能なオブジェクトです。リスト、行、ファイル...これは、必要に応じて値を読み取ることができるため便利です。ただし、すべての値はメモリに格納されます。できれば多くの意味を持っています。
発電機
ジェネレーターも反復可能なオブジェクトですが、一度しか読み取ることができません。 これは、メモリに値を保存するのではなく、その場で値を生成するという事実によるものです。
>>> mygenerator = (x*x for x in range(3)) >>> for i in mygenerator : ... print(i) 0 1 4
すべてが同じですが、四角括弧の代わりに括弧が使用されます。 しかし、ジェネレーターは一度しか使用できないため、mygeneratorコンストラクトにfor iを2回適用することはできません:ジェネレーターは0を計算し、それを忘れて1を計算し、計算4-次々に終了します
収量
Yieldは、おおよそreturnとして使用されるキーワードです-違いは、関数がジェネレーターを返すことです。
>>> def createGenerator() : ... mylist = range(3) ... for i in mylist : ... yield i*i ... >>> mygenerator = createGenerator() # >>> print(mygenerator) # mygenerator ! <generator object createGenerator at 0xb7555c34> >>> for i in mygenerator: ... print(i) 0 1 4
この場合、この例は役に立たないが、関数が一度だけ読み取る必要がある大きな値のセットを返すことを知っていると便利です。
yieldをマスターするには、関数を呼び出すときに関数本体内のコードが実行されないことを理解する必要があります。 この関数はジェネレーターオブジェクトのみを返します-少し注意が必要です:-)
ジェネレーターを呼び出すたびにコードが呼び出されます。
今、難しい部分:
初めて関数を実行すると、関数は最初からyieldでつまずく瞬間まで実行されます-その後、ループから最初の値を返します。 次の呼び出しごとに、記述したサイクルの別の反復が発生し、次の値が返されます-値がなくなるまで続きます。
関数コードの実行時にyieldが発生しないとすぐに、ジェネレーターは空と見なされます。 これは、ループの終了、またはif / else条件の一部が満たされない場合に発生する可能性があります。
ソースの質問からのコードの説明
ジェネレーター:
# , 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 = list(), [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
このコードには、いくつかの小さな部分が含まれています。
- ループはリストを反復しますが、反復中にリストが拡張します:-)これは、グループ化されたデータをすべてバイパスする簡潔な方法です。これは、無限ループになる可能性があるため、少し危険です。 この場合、候補者.extend(node._get_child_candidates(distance、min_dist、max_dist))はすべてのジェネレーター値を使い果たしますが、以前の値とは異なる値を与える新しいジェネレーターオブジェクトを作成し続けます(他のノードに適用されるため)。
- extend()メソッドは、反復可能な入力を期待し、その値をリストに追加するリストオブジェクトメソッドです。
通常、私たちは彼にリストを与えます:
>>> a = [1, 2] >>> b = [3, 4] >>> a.extend(b) >>> print(a) [1, 2, 3, 4]
しかし、このコードでは、ジェネレーターを受け入れます。これは、次の理由で優れています。
- 値を2回読み取る必要はありません。
- 多くの子孫があり、それらをすべてメモリに保存したくない場合があります。
Pythonはこのメソッドへの引数がリストであるかどうかを気にしないので、これは機能します。 Pythonは反復可能なオブジェクトを想定しているため、これは文字列、リスト、タプル、ジェネレーターで動作します! これはダックタイピングと呼ばれ、Pythonがとてもクールな理由の1つです。 しかし、これは別の質問の別の話です...
読者はここでやめるか、ジェネレーターの高度な使用についてもう少し読むことができます。
発電機の消耗制御
>>> class Bank(): # , (ATM — Automatic Teller Machine) ... crisis = False ... def create_atm(self) : ... while not self.crisis : ... yield "$100" >>> hsbc = Bank() # , >>> 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 # , ! >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> wall_street_atm = hsbc.create_atm() # >>> print(wall_street_atm.next()) <type 'exceptions.StopIteration'> >>> hsbc.crisis = False # , , - ... >>> print(corner_street_atm.next()) <type 'exceptions.StopIteration'> >>> brand_new_atm = hsbc.create_atm() # , ! >>> for cash in brand_new_atm : ... print cash $100 $100 $100 $100 $100 $100 $100 $100 $100 ...
これは、リソースへのアクセスの制御など、さまざまな目的に役立ちます。
あなたの親友Itertools
itertoolsモジュールには、反復可能なオブジェクトを操作するための特別な関数が含まれています。 ジェネレーターを複製しますか? 2つの発電機を直列に接続しますか? ネストされたリストの値を1行にグループ化しますか? 別のリストを作成せずにマップまたはzipを適用しますか?
インポート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ループの仕組みに関する記事を参照してください。