[Python] ماذا تفعل الكلمة الرئيسية "العائد"؟


Answers

اختصار Grokking

عندما ترى وظيفة ذات بيانات yield ، قم بتطبيق هذه الخدعة السهلة لفهم ما سيحدث:

  1. أدخل result = [] خطية result = [] في بداية الوظيفة.
  2. استبدل كل yield expr بـ result.append(expr) .
  3. إدراج return result السطر في أسفل الدالة.
  4. ياي - لا مزيد من تصريحات yield ! اقرأ واكتشف الشفرة.
  5. قارن الوظيفة بالتعريف الأصلي.

قد تعطيك هذه الخدعة فكرة عن المنطق وراء الوظيفة ، ولكن ما يحدث بالفعل مع yield يختلف اختلافًا كبيرًا عما يحدث في النهج القائم على القائمة. في كثير من الحالات ، سيكون أسلوب الإنتاجية أكثر فاعلية في الذاكرة وأسرع أيضًا. في حالات أخرى ، ستجعلك هذه الخدعة تمسك بحلقة لا نهائية ، على الرغم من أن الوظيفة الأصلية تعمل بشكل جيد. تابع القراءة لمعرفة المزيد...

لا تخلط بين Iterables و Iterators و Generators

أولا ، بروتوكول المكرر - عندما تكتب

for x in mylist:
    ...loop body...

تقوم بايثون بتنفيذ الخطوتين التاليتين:

  1. يحصل على مكرر mylist :

    استدعاء iter(mylist) -> يؤدي هذا إلى إرجاع كائن بطريقة next() (أو __next__() في Python 3).

    [هذه هي الخطوة التي ينسى معظم الناس إخبارك بها]

  2. يستخدم المكرّر لحلقة عبر العناصر:

    استمر في استدعاء الأسلوب next() على المكرر الذي تم إرجاعه من الخطوة 1. يتم تعيين قيمة الإرجاع من next() إلى x ويتم تنفيذ نص الحلقة. إذا تم رفع استثناء StopIteration من داخل next() ، فهذا يعني أنه لا توجد قيم أخرى في المكرر ويتم إنهاء الحلقة.

والحقيقة هي أن Python تقوم بتنفيذ الخطوتين السابقتين في أي وقت تريد فيه التكرار فوق محتويات كائن ما - لذا يمكن أن تكون حلقة for for ، ولكن يمكن أيضًا أن تكون رمزًا مثل otherlist.extend(mylist) (حيث تكون otherlist قائمة Python) .

هنا mylist قابل للتكرار لأنه يطبق بروتوكول التكرار. في فئة محددة من قبل المستخدم ، يمكنك تنفيذ طريقة __iter__() لجعل مثيلات الفئة الخاصة بك __iter__() . يجب أن ترجع هذه الطريقة إلى تكرار . المكرر هو كائن له أسلوب next() . من الممكن تنفيذ __iter__() و next() على نفس الفئة ، ويكون __iter__() بإرجاع self . سيعمل ذلك مع الحالات البسيطة ، ولكن ليس عندما تريد تكرار متكرران على نفس الكائن في نفس الوقت.

إذن ، هذا هو بروتوكول التكرار ، تقوم العديد من الكائنات بتنفيذ هذا البروتوكول:

  1. قوائم مدمجة ، وقواميس ، و tuples ، ومجموعات ، وملفات.
  2. الطبقات المعرفة من قبل المستخدم التي تنفذ __iter__() .
  3. مولدات كهرباء.

لاحظ أن حلقة for لا تعرف أي نوع من الكائنات التي تتعامل معها - إنها فقط تتبع بروتوكول التكرار ، ويسعدها الحصول على عنصر ما بعد العنصر الذي يطلق عليه next() . ترجع القوائم المضمنة عناصرها واحدة تلو الأخرى ، والقواميس تعيد المفاتيح واحدة تلو الأخرى ، وتعيد الملفات الخطوط الواحدة تلو الأخرى ، وما إلى ذلك تعود المولدات ... وهذا هو المكان الذي تأتي فيه yield :

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

بدلاً من عبارات yield ، إذا كان لديك ثلاثة عبارات return في f123() فقط سيتم تنفيذ التنفيذ الأول ، f123() الدالة. لكن f123() ليست وظيفة عادية. عندما يتم استدعاء f123() ، فإنه لا يقوم بإرجاع أي من القيم في عبارات العائد! تقوم بإرجاع كائن مولد. أيضا ، فإن وظيفة لا تخرج حقا - يذهب إلى حالة تعليق. عندما تحاول الحلقة for loop over the generator object ، تستأنف الوظيفة من حالتها المعلّقة في السطر التالي بعد yield الذي كانت تعود إليه من قبل ، وتنفذ السطر التالي من الشفرة ، وفي هذه الحالة بيان yield ، وترجع ذلك البند التالي. يحدث هذا حتى يتم إنهاء الوظيفة ، وعند هذه النقطة يقوم المولد برفع StopIteration ، ويتم الخروج من الحلقة.

لذا فإن كائن المولد يشبه المحول - في أحد طرفيه فإنه يعرض بروتوكول التكرار ، بتعريض __iter__() وطرق next() للاحتفاظ بالحلقة السعيدة. في الطرف الآخر ، فإنه يعمل على الوظيفة فقط بما يكفي للحصول على القيمة التالية للخروج منه ، ويعيده إلى وضع التعليق.

لماذا استخدام المولدات؟

عادة يمكنك كتابة التعليمات البرمجية التي لا تستخدم المولدات ولكن تطبق نفس المنطق. أحد الخيارات هو استخدام القائمة المؤقتة "الحيلة" التي ذكرتها من قبل. لن يعمل ذلك في جميع الحالات ، على سبيل المثال ، إذا كانت لديك حلقات غير محدودة ، أو قد يؤدي إلى استخدام غير فعال للذاكرة عندما يكون لديك قائمة طويلة بالفعل. النهج الآخر هو تطبيق فئة جديدة قابلة للتكرار SomethingIter تحافظ على الحالة في الأعضاء على سبيل المثال وتقوم بتنفيذ الخطوة المنطقية التالية في الطريقة next() أو ( __next__() في Python 3). اعتمادًا على المنطق ، قد ينتهي الأمر بالشفرة داخل الطريقة next() تبدو شديدة التعقيد وتكون عرضة للأخطاء. هنا المولدات توفر حلا نظيفا وسهلا.

Question

ما هو استخدام الكلمة الرئيسية في بيثون؟ ماذا تعمل، أو ماذا تفعل؟

على سبيل المثال ، أحاول فهم هذا الرمز 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. رمز يأتي من Jochen شولز (jrschulz) ، الذي جعل مكتبة بايثون كبيرة للمساحات مترية. هذا هو الرابط إلى المصدر الكامل: وحدة mspace .




TL;DR

When you find yourself building a list from scratch...

def squares_list(n):
    the_list = []                         # Replace
    for x in range(n):
        y = x * x
        the_list.append(y)                # these
    return the_list                       # lines

... yield each piece instead

def squares_the_yield_way(n):
    for x in range(n):
        y = x * x
        yield y                           # with this

This was my first "aha" moment with yield.

yield is a sugary way to say

build a series of stuff

Same behavior:

>>> for square in squares_list(4):
...     print(square)
...
0
1
4
9
>>> for square in squares_the_yield_way(4):
...     print(square)
...
0
1
4
9

Different behavior:

Yield is single-pass : you can only iterate through once. When a function has a yield in it we call it a generator function . And an iterator is what it returns. That's revealing. We lose the convenience of a container, but gain the power of an arbitrarily long series.

Yield is lazy , it puts off computation. A function with a yield in it doesn't actually execute at all when you call it. The iterator object it returns uses magic to maintain the function's internal context. Each time you call next() on the iterator (this happens in a for-loop) execution inches forward to the next yield. ( return raises StopIteration and ends the series.)

Yield is versatile . It can do infinite loops:

>>> def squares_all_of_them():
...     x = 0
...     while True:
...         yield x * x
...         x += 1
...
>>> squares = squares_all_of_them()
>>> for _ in range(4):
...     print(next(squares))
...
0
1
4
9

If you need multiple passes and the series isn't too long, just call list() on it:

>>> list(squares_the_yield_way(4))
[0, 1, 4, 9]

Brilliant choice of the word yield because both meanings apply:

yield — produce or provide (as in agriculture)

...provide the next data in the series.

yield — give way or relinquish (as in political power)

...relinquish CPU execution until the iterator advances.




All great answers whereas a bit difficult for newbies.

I assume you have learned return statement.
As an analogy, return and yield are twins.
return means 'Return and Stop' whereas 'yield` means 'Return but Continue'

  1. Try to get a num_list with return .
def num_list(n):
    for i in range(n):
        return i

شغلها:

In [5]: num_list(3)
Out[5]: 0

See, you get only a single number instead of a list of them,. return never allow you happy to prevail. It implemented once and quit.

  1. There comes yield

Replace return with yield

In [10]: def num_list(n):
    ...:     for i in range(n):
    ...:         yield i
    ...:

In [11]: num_list(3)
Out[11]: <generator object num_list at 0x10327c990> 

In [12]: list(num_list(3))
Out[12]: [0, 1, 2]

Now, you win to get all the numbers.
Comparing to return which runs once and stops, yield runs times you planed.
You can interpret return as return one of them ,
yield as return all of them . This is called iterable .

  1. One more step we can rewrite yield statement with return
In [15]: def num_list(n):
    ...:     result = []
    ...:     for i in range(n):
    ...:         result.append(i)
    ...:     return result

In [16]: num_list(3)
Out[16]: [0, 1, 2]

It's the core about yield .

The difference between a list return outputs and the object yield output is:
You can get [0, 1, 2] from a list object always whereas can only retrieve them from 'the object yield output' once.
So, it has a new name generator object as displayed in Out[11]: <generator object num_list at 0x10327c990> .

In conclusion as a metaphor to grok it,

return and yield are twins,
list and generator are twins.




From a programming viewpoint, the iterators are implemented as thunks

http://en.wikipedia.org/wiki/Thunk_(functional_programming)

To implement iterators/generators/thread pools for concurrent execution/etc as thunks (also called anonymous functions), one uses messages sent to a closure object, which has a dispatcher, and the dispatcher answers to "messages".

http://en.wikipedia.org/wiki/Message_passing

" next " is a message sent to a closure, created by " iter " call.

There are lots of ways to implement this computation. I used mutation but it is easy to do it without mutation, by returning the current value and the next yielder.

Here is a demonstration which uses the structure of R6RS but the semantics is absolutely identical as in python, it's the same model of computation, only a change in syntax is required to rewrite it in python.

Welcome to Racket v6.5.0.3.

-> (define gen
     (lambda (l)
       (define yield
         (lambda ()
           (if (null? l)
               'END
               (let ((v (car l)))
                 (set! l (cdr l))
                 v))))
       (lambda(m)
         (case m
           ('yield (yield))
           ('init  (lambda (data)
                     (set! l data)
                     'OK))))))
-> (define stream (gen '(1 2 3)))
-> (stream 'yield)
1
-> (stream 'yield)
2
-> (stream 'yield)
3
-> (stream 'yield)
'END
-> ((stream 'init) '(a b))
'OK
-> (stream 'yield)
'a
-> (stream 'yield)
'b
-> (stream 'yield)
'END
-> (stream 'yield)
'END
-> 



I was going to post "read page 19 of Beazley's 'Python: Essential Reference' for a quick description of generators", but so many others have posted good descriptions already.

Also, note that yield can be used in coroutines as the dual of their use in generator functions. Although it isn't the same use as your code snippet, (yield) can be used as an expression in a function. When a caller sends a value to the method using the send() method, then the coroutine will execute until the next (yield) statement is encountered.

Generators and coroutines are a cool way to set up data-flow type applications. I thought it would be worthwhile knowing about the other use of the yield statement in functions.




يتم تقليل الكلمة الرئيسية للعرض إلى حقيقتين بسيطتين:

  1. إذا اكتشف المحول البرمجي الكلمة الرئيسية yield أي مكان داخل إحدى الدالات ، فلن تعود هذه الدالة مرة أخرى عبر بيان return . بدلاً من ذلك ، تقوم على الفور بإرجاع كائن "قائمة معلقة" البطيئة يسمى مولد
  2. مولد هو التكرار. ما هو التكرار ؟ إنه شيء مثل list أو set أو range أو طريقة عرض dict-view ، مع بروتوكول مضمّن لزيارة كل عنصر بترتيب معين .

باختصار: المولد هو كسل ، قائمة معلقة تزايديًا ، وبيانات yield تسمح لك باستخدام التدوين الوظيفي لبرمجة قيم القائمة التي يجب أن يبثها المولد بشكل متزايد.

generator = myYieldingFunction(...)
x = list(generator)

   generator
       v
[x[0], ..., ???]

         generator
             v
[x[0], x[1], ..., ???]

               generator
                   v
[x[0], x[1], x[2], ..., ???]

                       StopIteration exception
[x[0], x[1], x[2]]     done

list==[x[0], x[1], x[2]]

مثال

دعونا تحديد وظيفة makeRange هذا تماما مثل range بايثون. استدعاء makeRange(n) العودة إلى مولد:

def makeRange(n):
    # return 0,1,2,...,n-1
    i = 0
    while i < n:
        yield i
        i += 1

>>> makeRange(5)
<generator object makeRange at 0x19e4aa0>

لإجبار المولد على إرجاع قيمه المعلقة على الفور ، يمكنك تمريرها إلى list() (تمامًا كما لو كنت تستطيع التكرار):

>>> list(makeRange(5))
[0, 1, 2, 3, 4]

مقارنة المثال بـ "فقط إرجاع قائمة"

يمكن اعتبار المثال أعلاه على أنه مجرد إنشاء قائمة يتم إلحاقها بها وإعادتها:

# list-version                   #  # generator-version
def makeRange(n):                #  def makeRange(n):
    """return [0,1,2,...,n-1]""" #~     """return 0,1,2,...,n-1"""
    TO_RETURN = []               #>
    i = 0                        #      i = 0
    while i < n:                 #      while i < n:
        TO_RETURN += [i]         #~         yield i
        i += 1                   #          i += 1  ## indented
    return TO_RETURN             #>

>>> makeRange(5)
[0, 1, 2, 3, 4]

هناك اختلاف رئيسي واحد ، على الرغم من ؛ انظر القسم الأخير.

كيف يمكنك استخدام المولدات

أحد الأمور المتكرّرة هو الجزء الأخير من فهم القائمة ، وجميع المولدات قابلة للتكرار ، لذلك يتم استخدامها غالبًا كما يلي:

#                   _ITERABLE_
>>> [x+10 for x in makeRange(5)]
[10, 11, 12, 13, 14]

للحصول على شعور أفضل للمولدات ، يمكنك الالتفاف على وحدة itertools (تأكد من استخدام chain . chain.from_iterable بدلاً من chain عند الضرورة). على سبيل المثال ، قد تستخدم حتى المولدات لتنفيذ قوائم البطيئة طويلة بشكل لا نهائي مثل itertools.count() . يمكنك تنفيذ def enumerate(iterable): zip(count(), iterable) الخاص بك def enumerate(iterable): zip(count(), iterable) ، أو بدلاً من ذلك القيام بذلك باستخدام الكلمة الأساسية yield في حلقة while.

يرجى ملاحظة: يمكن استخدام المولدات في الواقع للعديد من الأشياء الأخرى ، مثل تنفيذ برامج أو برامج غير حتمية أو أشياء أنيقة أخرى. ومع ذلك ، فإن وجهة نظر "القوائم الكسولة" التي أقدمها هنا هي الاستخدام الأكثر شيوعًا التي ستجدها.

خلف الكواليس

هذه هي الطريقة التي يعمل بها "بروتوكول التكرار Python". وهذا هو ، ما يحدث عند القيام list(makeRange(5)) . هذا هو ما وصفته في وقت سابق بأنه "قائمة كسول ، تدريجية".

>>> x=iter(range(5))
>>> next(x)
0
>>> next(x)
1
>>> next(x)
2
>>> next(x)
3
>>> next(x)
4
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

الدالة المضمنة next() فقط باستدعاء .next() الكائنات .next() ، والتي تعد جزءًا من "بروتوكول التكرار" ويتم العثور عليها في جميع المكررة. يمكنك استخدام الوظيفة next() وأجزاء أخرى من بروتوكول التكرار) لتنفيذ أشياء خيالية ، عادةً على حساب قابلية القراءة ، لذا حاول تجنب القيام بذلك ...

تفصيلات

عادة ، معظم الناس لا يهتمون بالفوارق التالية وربما يريدون التوقف عن القراءة هنا.

في لغة Python-speak ، يكون iterable أي كائن "يفهم مفهوم حلقة for-loop" مثل قائمة [1,2,3] ، ومكرر هو مثيل محدد للحلقة المطلوبة مثل [1,2,3].__iter__() . المولد هو نفسه تمامًا مثل أي مكرر ، باستثناء الطريقة التي تمت كتابتها (مع بناء جملة الدالة).

عند طلب مكرر من قائمة ، فإنه ينشئ مكرر جديد. ومع ذلك ، عندما تطلب مكررًا من مكرر (والذي نادرًا ما تقوم به) ، فإنه يمنحك نسخة من نفسه فقط.

وبالتالي ، في حالة غير المحتمل أنك فشلت في القيام بشيء من هذا القبيل ...

> x = myRange(5)
> list(x)
[0, 1, 2, 3, 4]
> list(x)
[]

... ثم تذكر أن المولد هو مكرر ؛ وهذا هو ، هو استخدام لمرة واحدة. إذا كنت ترغب في إعادة استخدامه ، يجب عليك الاتصال myRange(...) مرة أخرى. إذا كنت بحاجة إلى استخدام النتيجة مرتين ، قم بتحويل النتيجة إلى قائمة وقم بتخزينها في متغير x = list(myRange(5)) . يمكن لأولئك الذين يحتاجون إلى استنساخ مولد (على سبيل المثال ، الذين يقومون ببرمجة حكيمة مرعبة) استخدام itertools.tee إذا كان ذلك ضروريًا تمامًا ، حيث تم تأجيل اقتراح Python PEP itertools.tee .




Many people use return rather than yield but in some cases yield can be more efficient and easier to work with.

Here is an example which yield is definitely best for:

return (in function)

import random

def return_dates():
    dates = [] # with return you need to create a list then return it
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        dates.append(date)
    return dates

yield (in function)

def yield_dates():
    for i in range(5):
        date = random.choice(["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"])
        yield date # yield makes a generator automatically which works in a similar way, this is much more efficient

Calling functions

dates_list = return_dates()
print(dates_list)
for i in dates_list:
    print(i)

dates_generator = yield_dates()
print(dates_generator)
for i in  dates_generator:
    print(i)

Both functions do the same thing but yield uses 3 lines instead of 5 and has one less variable to worry about.

This is the result from the code:

As you can see both functions do the same thing, the only difference is return_dates() gives a list and yield_dates() gives a generator

A real life example would be something like reading a file line by line or if you just want to make a generator




yield is just like return - it returns whatever you tell it to. The only difference is that the next time you call the function, execution starts from the last call to the yield statement.

In the case of your code, the function get_child_candidates is acting like an iterator so that when you extend your list, it adds one element at a time to the new list.

list.extend calls an iterator until it's exhausted. In the case of the code sample you posted, it would be much clearer to just return a tuple and append that to the list.




While a lot of answers show why you'd use a yield to create a generator, there are more uses for yield . It's quite easy to make a coroutine, which enables the passing of information between two blocks of code. I won't repeat any of the fine examples that have already been given about using yield to create a generator.

To help understand what a yield does in the following code, you can use your finger to trace the cycle through any code that has a yield . Every time your finger hits the yield , you have to wait for a next or a send to be entered. When a next is called, you trace through the code until you hit the yield … the code on the right of the yield is evaluated and returned to the caller… then you wait. When next is called again, you perform another loop through the code. However, you'll note that in a coroutine, yield can also be used with a send … which will send a value from the caller into the yielding function. If a send is given, then yield receives the value sent, and spits it out the left hand side… then the trace through the code progresses until you hit the yield again (returning the value at the end, as if next was called).

فمثلا:

>>> def coroutine():
...     i = -1
...     while True:
...         i += 1
...         val = (yield i)
...         print("Received %s" % val)
...
>>> sequence = coroutine()
>>> sequence.next()
0
>>> sequence.next()
Received None
1
>>> sequence.send('hello')
Received hello
2
>>> sequence.close()



Here is a mental image of what yield does.

I like to think of a thread as having a stack (even when it's not implemented that way).

When a normal function is called, it puts its local variables on the stack, does some computation, then clears the stack and returns. The values of its local variables are never seen again.

With a yield function, when its code begins to run (ie after the function is called, returning a generator object, whose next() method is then invoked), it similarly puts its local variables onto the stack and computes for a while. But then, when it hits the yield statement, before clearing its part of the stack and returning, it takes a snapshot of its local variables and stores them in the generator object. It also writes down the place where it's currently up to in its code (ie the particular yield statement).

So it's a kind of a frozen function that the generator is hanging onto.

When next() is called subsequently, it retrieves the function's belongings onto the stack and re-animates it. The function continues to compute from where it left off, oblivious to the fact that it had just spent an eternity in cold storage.

Compare the following examples:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

When we call the second function, it behaves very differently to the first. The yield statement might be unreachable, but if it's present anywhere, it changes the nature of what we're dealing with.

>>> yielderFunction()
<generator object yielderFunction at 0x07742D28>

Calling yielderFunction() doesn't run its code, but makes a generator out of the code. (Maybe it's a good idea to name such things with the yielder prefix for readability.)

>>> gen = yielderFunction()
>>> dir(gen)
['__class__',
 ...
 '__iter__',    #Returns gen itself, to make it work uniformly with containers
 ...            #when given to a for loop. (Containers return an iterator instead.)
 'close',
 'gi_code',
 'gi_frame',
 'gi_running',
 'next',        #The method that runs the function's body.
 'send',
 'throw']

The gi_code and gi_frame fields are where the frozen state is stored. Exploring them with dir(..) , we can confirm that our mental model above is credible.




(My below answer only speaks from the perspective of using Python generator, not the underlying implementation of generator mechanism , which involves some tricks of stack and heap manipulation.)

When yield is used instead of a return in a python function, that function is turned into something special called generator function . That function will return an object of generator type. The yield keyword is a flag to notify the python compiler to treat such function specially. Normal functions will terminate once some value is returned from it. But with the help of the compiler, the generator function can be thought of as resumable. That is, the execution context will be restored and the execution will continue from last run. Until you explicitly call return, which will raise a StopIteration exception (which is also part of the iterator protocol), or reach the end of the function. I found a lot of references about generator but this one from the functional programming perspective is the most digestable.

(Now I want to talk about the rationale behind generator , and the iterator based on my own understanding. I hope this can help you grasp the essential motivation of iterator and generator. Such concept shows up in other languages as well such as C#.)

As I understand, when we want to process a bunch of data, we usually first store the data somewhere and then process it one by one. But this intuitive approach is problematic. If the data volume is huge, it's expensive to store them as a whole beforehand. So instead of storing the data itself directly, why not store some kind of metadata indirectly, ie the logic how the data is computed .

There are 2 approaches to wrap such metadata.

  1. The OO approach, we wrap the metadata as a class . This is the so-called iterator who implements the iterator protocol (ie the __next__() , and __iter__() methods). This is also the commonly seen iterator design pattern .
  2. The functional approach, we wrap the metadata as a function . This is the so-called generator function . But under the hood, the returned generator object still IS-A iterator because it also implements the iterator protocol.

Either way, an iterator is created, ie some object that can give you the data you want. The OO approach may be a bit complex. Anyway, which one to use is up to you.




For those who prefer a minimal working example, meditate on this interactive Python session:

>>> def f():
...   yield 1
...   yield 2
...   yield 3
... 
>>> g = f()
>>> for i in g:
...   print i
... 
1
2
3
>>> for i in g:
...   print i
... 
>>> # Note that this time nothing was printed



Yield is an Object

A return in a function will return a single value.

If you want function to return huge set of values use yield .

More importantly, yield is a barrier

like Barrier in Cuda Language, it will not transfer control until it gets completed.

ie

It will run the code in your function from the beginning until it hits yield . Then, it'll return the first value of the loop. Then, every other call will run the loop you have written in the function one more time, returning the next value until there is no value to return.




It's returning a generator. I'm not particularly familiar with Python, but I believe it's the same kind of thing as C#'s iterator blocks if you're familiar with those.

There's an IBM article which explains it reasonably well (for Python) as far as I can see.

The key idea is that the compiler/interpreter/whatever does some trickery so that as far as the caller is concerned, they can keep calling next() and it will keep returning values - as if the generator method was paused . Now obviously you can't really "pause" a method, so the compiler builds a state machine for you to remember where you currently are and what the local variables etc look like. This is much easier than writing an iterator yourself.




Yet another TL;DR

iterator on list : next() returns the next element of the list

iterator generator : next() will compute the next element on the fly (execute code)

You can see the yield/generator as a way to manually run the control flow from outside (like continue loop 1 step), by calling next, however complex the flow.

NOTE: the generator is NOT a normal function, it remembers previous state like local variables (stack), see other answers or articles for detailed explanation, the generator can only be iterated on once . You could do without yield but it would not be as nice, so it can be considered 'very nice' language sugar.