Чтобы понять, что yieldзначит, вы должны понять, что такое генераторы . И прежде чем вы сможете понять генераторы, вы должны понять итерируемые .
итерируемыми
Когда вы создаете список, вы можете читать его элементы по одному. Чтение его элементов по одному называется итерацией:
>>> 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файлы ...
Эти итерации удобны, потому что вы можете читать их сколько угодно, но вы храните все значения в памяти, и это не всегда то, что вы хотите, когда у вас много значений.
Генераторы
Генераторы - это итераторы, вид итерации, который вы можете повторять только один раз . Генераторы не хранят все значения в памяти, они генерируют значения на лету :
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
Это то же самое, за исключением того, что вы использовали ()вместо []. НО, вы не можете выполнить for i in mygeneratorвторой раз, так как генераторы могут использоваться только один раз: они вычисляют 0, затем забывают об этом и вычисляют 1, и заканчивают вычислять 4, один за другим.
Уступать
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]
Но в вашем коде он получает генератор, что хорошо, потому что:
- Вам не нужно читать значения дважды.
- У вас может быть много детей, и вы не хотите, чтобы они все хранились в памяти.
И это работает, потому что Python не заботится, является ли аргумент метода списком или нет. Python ожидает итерации, поэтому он будет работать со строками, списками, кортежами и генераторами! Это называется утка и является одной из причин, почему Python такой крутой. Но это другая история, для другого вопроса ...
Вы можете остановиться здесь или прочитать немного, чтобы увидеть расширенное использование генератора:
Контроль истощения генератора
>>> 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 содержит специальные функции для управления итерациями. Вы когда-нибудь хотели дублировать генератор? Цепочка двух генераторов? Группировать значения во вложенном списке с одной линией? Map / Zipбез создания другого списка?
Тогда просто import itertools.
Пример? Давайте посмотрим возможные порядки заезда на скачки:
>>> 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(часто того же самого, но одной итерации позже). Это продолжается до тех пор, пока функция не завершится, и в этот момент генератор считается исчерпанным.