Чтобы понять, что 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
(часто того же самого, но одной итерации позже). Это продолжается до тех пор, пока функция не завершится, и в этот момент генератор считается исчерпанным.