Чтобы понять, что делает 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 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 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
Caller:
# 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 candidates 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
циклы .