سؤال

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

على سبيل المثال ، أحاول فهم هذا الرمز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 Schulz (Jrschulz) هذه القطعة ، التي صنعت مكتبة Python رائعة للمساحات المترية. هذا هو الرابط للمصدر الكامل: وحدة MSPACE.

هل كانت مفيدة؟

المحلول

لفهم ماذا 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

المتصل:

# 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]

ولكن في الكود الخاص بك يحصل على مولد ، وهو أمر جيد لأن:

  1. لا تحتاج إلى قراءة القيم مرتين.
  2. قد يكون لديك الكثير من الأطفال ولا تريد تخزينهم جميعًا في الذاكرة.

وهو يعمل لأن 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
...

ملحوظة: لبيثون 3 ، استخدمprint(corner_street_atm.__next__()) أو print(next(corner_street_atm))

يمكن أن يكون مفيدًا لأشياء مختلفة مثل التحكم في الوصول إلى مورد.

itertools ، أفضل صديق لك

تحتوي وحدة ITERTOOLS على وظائف خاصة لمعالجة TERABLES. هل ترغب في تكرار مولد؟ سلسلة اثنين من المولدات؟ قيم المجموعة في قائمة متداخلة مع خط واحد؟ 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__() طريقة). التكرار هي أي كائنات يمكنك الحصول عليها من. المتكررون هم كائنات تتيح لك التكرار على Teerables.

هناك المزيد حول هذا الموضوع في هذا المقال كيف for الحلقات تعمل.

نصائح أخرى

اختصار للتفاهم yield

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

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

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

لا تخلط بين التكرار والتكرار والمولدات

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

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

Python يؤدي الخطوتين التاليتين:

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

    مكالمة iter(mylist) -> هذا يعيد كائن مع أ next() الطريقة (أو __next__() في بيثون 3).

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

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

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

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

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

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

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

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

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

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

أعتقد أنه من هذا الطريق:

التكرار هو مجرد مصطلح رائع لكائن يحتوي على ملف next() طريقة. لذلك تنتهي وظيفة العائد إلى أن تكون شيئًا من هذا القبيل:

نسخة أصلية:

def some_function():
    for i in xrange(4):
        yield i

for i in some_function():
    print i

هذا هو في الأساس ما يفعله مترجم بيثون مع الكود أعلاه:

class it:
    def __init__(self):
        # Start at -1 so that we get 0 when we add 1 below.
        self.count = -1

    # The __iter__ method will be called once by the 'for' loop.
    # The rest of the magic happens on the object returned by this method.
    # In this case it is the object itself.
    def __iter__(self):
        return self

    # The next method will be called repeatedly by the 'for' loop
    # until it raises StopIteration.
    def next(self):
        self.count += 1
        if self.count < 4:
            return self.count
        else:
            # A StopIteration exception is raised
            # to signal that the iterator is done.
            # This is caught implicitly by the 'for' loop.
            raise StopIteration

def some_func():
    return it()

for i in some_func():
    print i

لمزيد من البصيرة حول ما يحدث وراء الكواليس ، for يمكن إعادة كتابة الحلقة إلى هذا:

iterator = some_func()
try:
    while 1:
        print iterator.next()
except StopIteration:
    pass

هل هذا أكثر منطقية أم يخلطك أكثر؟ قون

يجب أن أشير إلى أن هذا هو تبسيط مفرط لأغراض توضيحية. قون

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

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

شيء صغير: المولد هو قائمة كسول ، معلقة بشكل متزايد, ، و 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.from_iterable عوضا عن chain عندما مبرر). على سبيل المثال ، قد تستخدم المولدات لتنفيذ قوائم كسول طويلة بلا حدود itertools.count(). يمكنك تنفيذ خاصتك def enumerate(iterable): zip(count(), iterable), ، أو بدلاً من ذلك القيام بذلك مع yield الكلمة الرئيسية في حلقة الوقت.

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


خلف الكواليس

هذه هي الطريقة التي يعمل بها "بروتوكول تكرار بيثون". هذا هو ما يحدث عندما تفعل 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() وظيفة (وأجزاء أخرى من بروتوكول التكرار) لتنفيذ الأشياء الفاخرة ، عادة على حساب قابلية القراءة ، لذلك حاول تجنب القيام بذلك ...


minutiae

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

في Python-speak ، متوقعة هو أي كائن "يفهم مفهوم الحلقة" مثل القائمة [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 القابل للنسخ بيب تم تأجيل اقتراح المعايير.

ماذا يكون ال yield الكلمة الرئيسية تفعل في بيثون؟

الإجابة الخطوط العريضة/الملخص

  • وظيفة مع yield, ، عند استدعاء ، إرجاع أ مولد كهرباء.
  • المولدات هي تكرار لأنها تنفذ بروتوكول التكرار, ، حتى تتمكن من التكرار عليها.
  • يمكن أن يكون المولد أيضًا أرسل المعلومات, وجعلها من الناحية المفاهيمية كوروتين.
  • في بيثون 3 ، يمكنك مندوب من مولد إلى آخر في كلا الاتجاهين مع yield from.
  • (ينتقد التذييل بعض الإجابات ، بما في ذلك العائد ، ويناقش استخدام return في مولد.)

مولدات كهرباء:

yield هو فقط قانوني داخل تعريف الوظيفة ، و ضم او احتواء ال yield في تعريف الوظيفة يجعله يعيد مولدًا.

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

yield يوفر طريقة سهلة تنفيذ بروتوكول ITERATAR, ، المحددة بالطريقتين التاليتين:__iter__ و next (بيثون 2) أو __next__ (بيثون 3). كلتا الطريقتين تجعل كائنًا كائنًا يمكن أن تكتب مع Iterator فئة قاعدة مجردة من collections وحدة.

>>> def func():
...     yield 'I am'
...     yield 'a generator!'
... 
>>> type(func)                 # A function with yield is still a function
<type 'function'>
>>> gen = func()
>>> type(gen)                  # but it returns a generator
<type 'generator'>
>>> hasattr(gen, '__iter__')   # that's an iterable
True
>>> hasattr(gen, 'next')       # and with .next (.__next__ in Python 3)
True                           # implements the iterator protocol.

نوع المولد هو نوع فرعي من المؤلف:

>>> import collections, types
>>> issubclass(types.GeneratorType, collections.Iterator)
True

وإذا لزم الأمر ، يمكننا الكتابة مثل هذا:

>>> isinstance(gen, types.GeneratorType)
True
>>> isinstance(gen, collections.Iterator)
True

ميزة من Iterator هل هذا بمجرد استنفاده, ، لا يمكنك إعادة استخدامه أو إعادة ضبطه:

>>> list(gen)
['I am', 'a generator!']
>>> list(gen)
[]

سيكون عليك صنع آخر إذا كنت تريد استخدام وظائفه مرة أخرى (انظر الحاشية 2):

>>> list(func())
['I am', 'a generator!']

يمكن للمرء أن يحقق البيانات برمجيا ، على سبيل المثال:

def func(an_iterable):
    for item in an_iterable:
        yield item

المولد البسيط أعلاه يعادل أيضًا أدناه - اعتبارًا من Python 3.3 (وليس متاحًا في Python 2) ، يمكنك استخدامه yield from:

def func(an_iterable):
    yield from an_iterable

لكن، yield from كما يسمح للتفويض بالولايات الفرعية ، والتي سيتم شرحها في القسم التالي حول الوفد التعاوني مع الكوروتين الفرعيين.

coroutines:

yield يشكل تعبيرًا يسمح بإرسال البيانات إلى المولد (انظر الحاشية 3)

فيما يلي مثال ، لاحظ received متغير ، والذي سيشير إلى البيانات التي يتم إرسالها إلى المولد:

def bank_account(deposited, interest_rate):
    while True:
        calculated_interest = interest_rate * deposited 
        received = yield calculated_interest
        if received:
            deposited += received


>>> my_account = bank_account(1000, .05)

أولاً ، يجب علينا وضع طوابير المولد مع وظيفة مدمجة ، next. سوف تسمي ما هو مناسب next أو __next__ الطريقة ، اعتمادًا على إصدار Python الذي تستخدمه:

>>> first_year_interest = next(my_account)
>>> first_year_interest
50.0

والآن يمكننا إرسال البيانات إلى المولد. ((إرسال None هو نفس الاتصال next.) :

>>> next_year_interest = my_account.send(first_year_interest + 1000)
>>> next_year_interest
102.5

تفويض تعاوني إلى كوروتين مع yield from

الآن ، تذكر ذلك yield from متوفر في Python 3. وهذا يسمح لنا بتفويض coroutines إلى أكووروتين:

def money_manager(expected_rate):
    under_management = yield     # must receive deposited value
    while True:
        try:
            additional_investment = yield expected_rate * under_management 
            if additional_investment:
                under_management += additional_investment
        except GeneratorExit:
            '''TODO: write function to send unclaimed funds to state'''
        finally:
            '''TODO: write function to mail tax info to client'''


def investment_account(deposited, manager):
    '''very simple model of an investment account that delegates to a manager'''
    next(manager) # must queue up manager
    manager.send(deposited)
    while True:
        try:
            yield from manager
        except GeneratorExit:
            return manager.close()

والآن يمكننا تفويض الوظائف إلى المولد الفرعي ويمكن استخدامه بواسطة مولد كما هو مذكور أعلاه:

>>> my_manager = money_manager(.06)
>>> my_account = investment_account(1000, my_manager)
>>> first_year_return = next(my_account)
>>> first_year_return
60.0
>>> next_year_return = my_account.send(first_year_return + 1000)
>>> next_year_return
123.6

يمكنك قراءة المزيد عن الدلالات الدقيقة لـ yield from في بيب 380.

طرق أخرى: إغلاق ورمي

ال close الطريقة ترفع GeneratorExit عند النقطة ، تم تجميد تنفيذ الوظيفة. وسيتم استدعاء هذا أيضا من قبل __del__ لذلك يمكنك وضع أي رمز تنظيف حيث تتعامل مع GeneratorExit:

>>> my_account.close()

يمكنك أيضًا رمي استثناء يمكن معالجته في المولد أو نشره مرة أخرى إلى المستخدم:

>>> import sys
>>> try:
...     raise ValueError
... except:
...     my_manager.throw(*sys.exc_info())
... 
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "<stdin>", line 2, in <module>
ValueError

استنتاج

أعتقد أنني غطيت جميع جوانب السؤال التالي:

ماذا يكون ال yield الكلمة الرئيسية تفعل في بيثون؟

لقد أتضح أن yield يفعل الكثير. أنا متأكد من أنه يمكنني إضافة أمثلة أكثر شمولية لهذا. إذا كنت تريد المزيد أو لديك بعض الانتقادات البناءة ، فأخبرني بالتعليق أدناه.


زائدة:

نقد الجواب العلوي/المقبول **

  • إنه مرتبك على ما يجعل متوقعة, ، فقط باستخدام قائمة كمثال. انظر مراجعاتي أعلاه ، ولكن باختصار: يحتوي iferable على __iter__ طريقة العودة التكرار. و التكرار يوفر أ .next (بيثون 2 أو .__next__ (Python 3) الطريقة ، والتي تسمى ضمنيًا بواسطة for الحلقات حتى ترفع StopIteration, ، وبمجرد القيام بذلك ، ستستمر في القيام بذلك.
  • ثم يستخدم تعبير المولد لوصف ماهية المولد. نظرًا لأن المولد هو ببساطة وسيلة مريحة لإنشاء ملف التكرار, ، إنه يربك الأمر فقط ، وما زلنا لم نصل بعد إلى yield جزء.
  • في السيطرة على استنفاد المولد يسمي .next الطريقة ، عندما يكون بدلاً من ذلك يجب أن يستخدم وظيفة مدمجة ، next. ستكون طبقة مناسبة من عدم التوجيه ، لأن رمزه لا يعمل في بيثون 3.
  • itertools؟ هذا لم يكن ذا صلة بما yield يفعل على الإطلاق.
  • لا مناقشة للطرق التي yield يوفر جنبا إلى جنب مع الوظيفة الجديدة yield from في بيثون 3. الإجابة العلوية/المقبولة هي إجابة غير مكتملة للغاية.

نقد الإجابة تشير yield في تعبير المولد أو الفهم.

تسمح القواعد النحوية حاليًا بأي تعبير في فهم القائمة.

expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) |
                     ('=' (yield_expr|testlist_star_expr))*)
...
yield_expr: 'yield' [yield_arg]
yield_arg: 'from' test | testlist

نظرًا لأن العائد هو تعبير ، فقد تم وصفه من قبل البعض للاهتمام لاستخدامه في الشمولية أو التعبير المولد - على الرغم من عدم وجود حالة استخدام جيدة بشكل خاص.

مطورو Cpython Core هم مناقشة إهمال بدلها. إليك منشور ذي صلة من القائمة البريدية:

في 30 يناير 2017 الساعة 19:05 ، كتب بريت كانون:

على Sun ، 29 يناير 2017 في 16:39 كتب كريج رودريغز:

أنا بخير مع أي نهج. ترك الأشياء بالطريقة التي هم بها في بيثون 3 ليست جيدة ، IMHO.

تصويتي هو أنه بناء جملة لأنك لا تحصل على ما تتوقعه من بناء الجملة.

أوافق على أن هذا مكان معقول حتى ينتهي بنا المطاف ، لأن أي رمز يعتمد على السلوك الحالي هو حقًا ذكي للغاية بحيث لا يمكن الحفاظ عليه.

فيما يتعلق بالوصول إلى هناك ، من المحتمل أن نريد:

  • بناء الجملة أو deprecationwarning في 3.7
  • تحذير PY3K في 2.7.x
  • بناء الجملة في 3.8

هتاف ، نيك.

- نيك كوجلان | ncoghlan في gmail.com | بريسبان، أستراليا

علاوة على ذلك ، هناك القضية المتميزة (10544) الذي يبدو أنه يشير في اتجاه هذا أبداً كونك فكرة جيدة (Pypy ، تطبيق Python مكتوب في Python ، يرفع بالفعل تحذيرات بناء الجملة.)

خلاصة القول ، حتى يخبرنا مطورو Cpython بخلاف ذلك: لا تضع yield في تعبير المولد أو الفهم.

ال return بيان في مولد

في بيثون 2:

في وظيفة المولد ، return البيان غير مسموح بتضمين expression_list. في هذا السياق ، عارية return يشير إلى أن المولد قد تم ذلك وسيسبب StopIteration يرفع.

و expression_list هو في الأساس أي عدد من التعبيرات مفصولة بفواصل - في الأساس ، في بيثون 2 ، يمكنك إيقاف المولد مع return, ، لكن لا يمكنك إرجاع قيمة.

في بيثون 3:

في وظيفة المولد ، return يشير البيان إلى أن المولد قد انتهى وسيسبب StopIteration يرفع. يتم استخدام القيمة التي تم إرجاعها (إن وجدت) كوسيطة لبناء StopIteration ويصبح StopIteration.value ينسب.

الحواشي

  1. تمت الإشارة إلى اللغات Clu و Sather و Icon في الاقتراح لتقديم مفهوم المولدات إلى Python. الفكرة العامة هي أن الوظيفة يمكن أن تحافظ على الحالة الداخلية وإعطاء نقاط بيانات وسيطة عند الطلب من قبل المستخدم. هذا وعد أن يكون متفوقة في الأداء على أساليب أخرى ، بما في ذلك خيوط الثعبان, ، والتي ليست متوفرة حتى في بعض الأنظمة.

  2. هذا يعني ، على سبيل المثال ، ذلك xrange أشياء (range في بيثون 3) ليسوا IteratorS ، على الرغم من أنها لا تشوبها شائبة ، لأنه يمكن إعادة استخدامها. مثل القوائم ، لهم __iter__ الأساليب إرجاع كائنات التكرار.

  3. yield تم تقديمه في الأصل كبيان ، مما يعني أنه لا يمكن أن يظهر إلا في بداية سطر في كتلة التعليمات البرمجية. حاليا yield يخلق تعبير العائد.https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt كان هذا التغيير مقترح للسماح للمستخدم بإرسال البيانات إلى المولد تمامًا كما قد يستقبلها المرء. لإرسال البيانات ، يجب أن يكون المرء قادرًا على تعيينها لشيء ما ، ولهذا ، لن ينجح البيان.

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

في حالة الكود الخاص بك ، الوظيفة get_child_candidates يتصرف مثل التكرار بحيث عند تمديد قائمتك ، فإنه يضيف عنصرًا واحدًا في وقت واحد إلى القائمة الجديدة.

list.extend يدعو التكرار حتى يتم استنفاده. في حالة عينة الكود التي نشرتها ، سيكون من الواضح كثيرًا فقط لإعادة توبلي وإلحاق ذلك بالقائمة.

هناك شيء إضافي يجب ذكره: وظيفة لا تضطر في الواقع إلى إنهاءها. لقد كتبت رمز مثل هذا:

def fib():
    last, cur = 0, 1
    while True: 
        yield cur
        last, cur = cur, last + cur

ثم يمكنني استخدامه في رمز آخر مثل هذا:

for f in fib():
    if some_condition: break
    coolfuncs(f);

إنه يساعد حقًا في تبسيط بعض المشكلات ، ويجعل بعض الأشياء أسهل في العمل معها.

بالنسبة لأولئك الذين يفضلون مثالًا على الحد الأدنى من العمل ، يتأملون في جلسة بيثون التفاعلية هذه:

>>> 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

TL ؛ د

بدلا من هذا:

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

افعل هذا:

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

كلما وجدت نفسك تبني قائمة من الصفر ، yield كل قطعة بدلا من ذلك.

كانت هذه أول لحظة "آها" مع العائد.


yield هو السكرية طريقة لقول

بناء سلسلة من الأشياء

نفس السلوك:

>>> for square in square_list(4):
...     print(square)
...
0
1
4
9
>>> for square in square_yield(4):
...     print(square)
...
0
1
4
9

سلوك مختلف:

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

العائد كسول, ، فإنه يؤجل الحساب. وظيفة مع العائد فيه لا تنفذ فعليًا على الإطلاق عند الاتصال به. يعود كائن التكرار هذا يتذكر حيث توقفت. في كل مرة تتصل بها next() على المؤلف (يحدث هذا في بوصة تنفيذ من أجل الحلقة) إلى الأمام إلى العائد التالي. return يرفع التوقف وينتهي من السلسلة (هذه هي الطرف الطبيعي للحلقة).

العائد متعدد الجوانب والاستعمالات. لا يجب تخزين البيانات جميعًا معًا ، يمكن توفيرها واحدًا في كل مرة. يمكن أن يكون لانهائي.

>>> 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

اذا احتجت تمريرات متعددة والسلسلة ليست طويلة جدًا ، فقط اتصل list() عليه:

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

اختيار رائع للكلمة yield لان كلا المعاني يتقدم:

أَثْمَر - إنتاج أو توفير (كما في الزراعة)

... توفير البيانات التالية في السلسلة.

أَثْمَر - أفسح المجال أو التنازل (كما في السلطة السياسية)

... قم بالتخلي عن تنفيذ وحدة المعالجة المركزية حتى يتقدم المتكرر.

العائد يمنحك مولد.

def get_odd_numbers(i):
    return range(1, i, 2)
def yield_odd_numbers(i):
    for x in range(1, i, 2):
       yield x
foo = get_odd_numbers(10)
bar = yield_odd_numbers(10)
foo
[1, 3, 5, 7, 9]
bar
<generator object yield_odd_numbers at 0x1029c6f50>
bar.next()
1
bar.next()
3
bar.next()
5

كما ترون ، في الحالة الأولى foo يحمل القائمة بأكملها في الذاكرة في وقت واحد. إنها ليست صفقة كبيرة لقائمة مع 5 عناصر ، ولكن ماذا لو كنت تريد قائمة 5 ملايين؟ لا يقتصر الأمر على أن يكون هذا آيانًا ضخمًا للذاكرة ، بل يكلف أيضًا الكثير من الوقت للبناء في الوقت الذي تسمى الوظيفة.

في الحالة الثانية ، bar فقط يعطيك مولد. المولد هو أمر لا يطاق-مما يعني أنه يمكنك استخدامه في أ for حلقة ، إلخ ، ولكن لا يمكن الوصول إلى كل قيمة إلا مرة واحدة. لا يتم تخزين جميع القيم أيضًا في الذاكرة في نفس الوقت ؛ كائن المولد "يتذكر" المكان الذي كان فيه في الحلقة الأخيرة التي أطلقتها عليه-بهذه الطريقة ، إذا كنت تستخدم العد المنتظم (على سبيل المثال) إلى 50 مليار في وقت واحد وتخزين 50 مليار رقم ليحسب من خلال.

مرة أخرى ، هذا مثال مفتعل للغاية ، فمن المحتمل أنك ستستخدم Itertools إذا كنت ترغب حقًا في العد إلى 50 مليار. قون

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

إنه يعيد مولد. أنا لست على دراية بشكل خاص بـ Python ، لكنني أعتقد أنه نفس النوع من الأشياء كتل كوكس C# إذا كنت معتادًا على هؤلاء.

الفكرة الرئيسية هي أن المترجم/المترجم المترجم/مهما كان بعض الخداع بحيث يتعلق بالمتصل ، يمكنهم الاستمرار في الاتصال التالي () وسيستمر في إرجاع القيم - كما لو تم إيقاف طريقة المولد. من الواضح الآن أنه لا يمكنك حقًا "إيقاف" طريقة ، وبالتالي فإن المترجم يبني جهاز حالة لك لتتذكر مكانك حاليًا وما تبدو المتغيرات المحلية وما إلى ذلك. هذا أسهل بكثير من كتابة التكرار بنفسك.

هناك نوع واحد من الإجابة التي لا أشعر أنها قد أعطيت حتى الآن ، من بين الإجابات العديدة العديدة التي تصف كيفية استخدام المولدات. إليكم إجابة نظرية لغة البرمجة:

ال yield بيان في بيثون يعيد مولد. مولد في بيثون هو وظيفة تعود الاستمرارية (وعلى وجه التحديد نوع من coroutine ، ولكن الاستمرارية تمثل الآلية الأكثر عمومية لفهم ما يجري).

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

يمكن تنفيذ الاستمرارية ، في هذا النموذج الأكثر عمومية ، بطريقتين. في ال call/cc الطريق ، يتم حفظ كومة البرنامج حرفيًا ، ثم عند استمرار الاستمرار ، يتم استعادة المكدس.

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

def save_file(filename):
  def write_file_continuation():
    write_stuff_to_file(filename)

  check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation)

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

سوف تصور ما تبقى من هذا المنشور ، دون فقدان عمومية ، الاستمرارية على أنها CPS ، لأنها جحيم أسهل بكثير لفهمها وقراءتها.


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

def f():
  while True:
    yield 4

من الواضح أن هذا أمر معقول يتم تعريف سلوكه جيدًا - في كل مرة يتكرر فيها المولد عليه ، يعود 4 (ويفعل ذلك إلى الأبد). لكن من المحتمل ألا يكون النوع الأولي من الثور الذي يتبادر إلى الذهن عند التفكير في التكرار (أي ، for x in collection: do_something(x)). يوضح هذا المثال قوة المولدات: إذا كان أي شيء هو التكرار ، فيمكن للمولد أن ينقذ حالة تكراره.

للتكرار: يمكن أن توفر الاستمرارية أن توفر حالة مكدس البرنامج والمولدات في حالة التكرار. هذا يعني أن الاستمرارية أكثر قوة من المولدات ، ولكن أيضًا أن المولدات أسهل كثيرًا. من الأسهل على مصمم اللغة تنفيذها ، ويسهل على المبرمج استخدامه (إذا كان لديك بعض الوقت للحرق ، فحاول القراءة والفهم هذه الصفحة حول الاستمرارية و Call/CC).

ولكن يمكنك بسهولة تنفيذ المولدات (وتصورها) كحالة بسيطة ومحددة من نمط تمرير الاستمرار:

كلما كان yield يسمى ، فهو يخبر الوظيفة لإعادة استمرار. عندما يتم استدعاء الوظيفة مرة أخرى ، تبدأ من أي مكان توقف. لذلك ، في رمز pseudo-pseudocod next الطريقة في الأساس على النحو التالي:

class Generator():
  def __init__(self,iterable,generatorfun):
    self.next_continuation = lambda:generatorfun(iterable)

  def next(self):
    value, next_continuation = self.next_continuation()
    self.next_continuation = next_continuation
    return value

أين ال yield الكلمة الرئيسية هي في الواقع سكر النحوي لوظيفة المولد الحقيقي ، في الأساس شيء مثل:

def generatorfun(iterable):
  if len(iterable) == 0:
    raise StopIteration
  else:
    return (iterable[0], lambda:generatorfun(iterable[1:]))

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

هنا مثال بلغة واضحة. سأقدم مراسلات بين المفاهيم البشرية عالية المستوى لمفاهيم بيثون منخفضة المستوى.

أريد أن أعمل على سلسلة من الأرقام ، لكنني لا أريد أن أزعج نفسي بإنشاء هذا التسلسل ، أريد فقط التركيز على العملية التي أريد القيام بها. لذلك ، أفعل ما يلي:

  • أدعوك وأخبرك أنني أريد سلسلة من الأرقام التي يتم إنتاجها بطريقة محددة ، وأعلمك ما هي الخوارزمية.
    هذه الخطوة تتوافق مع defining وظيفة المولد ، أي الوظيفة التي تحتوي على أ yield.
  • في وقت لاحق ، أقول لك ، "حسنًا ، استعد لإخبارنا بتسلسل الأرقام".
    تتوافق هذه الخطوة مع استدعاء وظيفة المولد التي تُرجع كائن المولد. لاحظ أنك لا تخبرني بأي أرقام حتى الآن ؛ أنت فقط تمسك الورق والقلم الرصاص.
  • أسألك ، "قل لي الرقم التالي" ، وتقول لي الرقم الأول ؛ بعد ذلك ، تنتظرني أن أسألك عن الرقم التالي. من وظيفتك أن تتذكر أين كنت ، وما هي الأرقام التي قلتها بالفعل ، وما هو الرقم التالي. لا يهمني التفاصيل.
    هذه الخطوة تتوافق مع الاتصال .next() على كائن المولد.
  • ... كرر الخطوة السابقة ، حتى ...
  • في النهاية ، قد تنتهي. أنت لا تخبرني رقمًا ؛ أنت فقط تصرخ ، "امسك خيولك! لقد انتهيت! لا مزيد من الأرقام!"
    هذه الخطوة تتوافق مع كائن المولد الذي ينهي وظيفته ، ورفع أ StopIteration استثناء لا تحتاج وظيفة المولد إلى رفع الاستثناء. يتم رفعه تلقائيًا عندما تنتهي الوظيفة أو تصدر أ return.

هذا ما يفعله المولد (وظيفة تحتوي على ملف yield) ؛ يبدأ التنفيذ ، ويتوقف مؤقتًا كلما فعل yield, ، وعندما طلب .next() القيمة تستمر من النقطة كانت الأخيرة. يتلاءم تمامًا مع التصميم مع بروتوكول Python ، والذي يصف كيفية طلب القيم بالتتابع.

المستخدم الأكثر شهرة لبروتوكول ITerator هو for القيادة في بيثون. لذا ، كلما فعلت:

for item in sequence:

لا يهم إذا sequence هي قائمة أو سلسلة أو قاموس أو مولد هدف كما هو موضح أعلاه ؛ والنتيجة هي نفسها: تقرأ عناصر من تسلسل واحد تلو الآخر.

لاحظ أن defining وظيفة تحتوي على ملف yield الكلمة الرئيسية ليست هي الطريقة الوحيدة لإنشاء مولد ؛ إنها مجرد أسهل طريقة لإنشاء واحدة.

للحصول على معلومات أكثر دقة ، اقرأ عن أنواع التكرار, ، ال بيان العائد و مولدات كهرباء في وثائق بيثون.

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

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

فمثلا:

>>> 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()

هناك آخر yield الاستخدام والمعنى (منذ Python 3.3):

yield from <expr>

من PEP 380 - بناء جملة للتفويض إلى المولد الفرعي:

يتم اقتراح بناء الجملة لمولد لتفويض جزء من عملياته إلى مولد آخر. يتيح هذا قسمًا من التعليمات البرمجية التي تحتوي على "العائد" لوضعها ووضعها في مولد آخر. بالإضافة إلى ذلك ، يُسمح للمولود الفرعي بالعودة بقيمة ، ويتم توفير القيمة لمولد التفويض.

يفتح بناء الجملة الجديد أيضًا بعض الفرص للتحسين عندما يعيد أحد المولدات التي تنتجها قيم أخرى.

علاوة على ذلك هذه سيقدم (منذ Python 3.5):

async def new_coroutine(data):
   ...
   await blocking_action()

لتجنب الخلط بين كوروتين مع مولد منتظم (اليوم yield يستخدم في كليهما).

جميع الإجابات الرائعة ، مهما كانت صعبة بعض الشيء على المبتدئين.

أفترض أنك تعلمت return بيان.

كقياس ، return و yield توأم. return يعني "العودة والتوقف" في حين أن "العائد" يعني "العودة ، ولكن تابع"

  1. حاول الحصول على num_list مع return.
def num_list(n):
    for i in range(n):
        return i

شغلها:

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

انظر ، تحصل على رقم واحد فقط بدلاً من قائمة منها. return لا تسمح لك أبدًا بالسعادة ، فقط تنفذ مرة واحدة وتوقف.

  1. جاءكم yield

يحل محل return مع 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]

الآن ، تربح للحصول على جميع الأرقام.

مقارنة مع return الذي يمتد مرة واحدة ويتوقف ، yield يدير الأوقات التي تخطيت فيها. يمكنك تفسير return كما return one of them, ، و yield كما return all of them. هذا يسمي iterable.

  1. خطوة أخرى يمكننا إعادة كتابة yield بيان مع 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]

إنه جوهر yield.

الفرق بين القائمة return المخرجات والكائن yield الإخراج هو:

ستحصل دائمًا على [0 ، 1 ، 2] من كائن قائمة ولكن يمكنك استردادها فقط من "الكائن yield الإخراج مرة واحدة. لذلك ، لديها اسم جديد generator كائن كما هو معروض في Out[11]: <generator object num_list at 0x10327c990>.

في الختام ، كاستعارة لتخليصه:

  • return و yield توأم
  • list و generator توأم

فيما يلي بعض الأمثلة Python حول كيفية تنفيذ المولدات فعليًا كما لو أن Python لم يوفر السكر النحوي لهم:

كمولد بيثون:

from itertools import islice

def fib_gen():
    a, b = 1, 1
    while True:
        yield a
        a, b = b, a + b

assert [1, 1, 2, 3, 5] == list(islice(fib_gen(), 5))

استخدام عمليات الإغلاق المعجمية بدلاً من المولدات

def ftake(fnext, last):
    return [fnext() for _ in xrange(last)]

def fib_gen2():
    #funky scope due to python2.x workaround
    #for python 3.x use nonlocal
    def _():
        _.a, _.b = _.b, _.a + _.b
        return _.a
    _.a, _.b = 0, 1
    return _

assert [1,1,2,3,5] == ftake(fib_gen2(), 5)

استخدام إغلاق الكائنات بدلاً من المولدات (لان incursandobjectsareequevalent)

class fib_gen3:
    def __init__(self):
        self.a, self.b = 1, 1

    def __call__(self):
        r = self.a
        self.a, self.b = self.b, self.a + self.b
        return r

assert [1,1,2,3,5] == ftake(fib_gen3(), 5)

كنت سأقوم بنشر "قراءة الصفحة 19 من" بيثون: المرجع الأساسي "من بيزلي للحصول على وصف سريع للمولدات" ، لكن الكثير من الآخرين نشروا أوصافًا جيدة بالفعل.

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

تعد المولدات و coroutines طريقة رائعة لإعداد تطبيقات نوع تدفق البيانات. اعتقدت أنه سيكون من المفيد معرفة الاستخدام الآخر ل yield بيان في الوظائف.

من وجهة نظر البرمجة ، يتم تنفيذ التكرار ثونكس.

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

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

"التالي"هل رسالة تم إرسالها إلى إغلاق ، تم إنشاؤها بواسطة"iter" مكالمة.

هناك الكثير من الطرق لتنفيذ هذا الحساب. لقد استخدمت طفرة ، لكن من السهل القيام بذلك دون طفرة ، من خلال إعادة القيمة الحالية والييلدر التالي.

فيما يلي مظاهرة تستخدم بنية R6Rs ، لكن الدلالات متطابقة تمامًا مع 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
->

اليك مثال بسيط:

def isPrimeNumber(n):
    print "isPrimeNumber({}) call".format(n)
    if n==1:
        return False
    for x in range(2,n):
        if n % x == 0:
            return False
    return True

def primes (n=1):
    while(True):
        print "loop step ---------------- {}".format(n)
        if isPrimeNumber(n): yield n
        n += 1

for n in primes():
    if n> 10:break
    print "wiriting result {}".format(n)

انتاج:

loop step ---------------- 1
isPrimeNumber(1) call
loop step ---------------- 2
isPrimeNumber(2) call
loop step ---------------- 3
isPrimeNumber(3) call
wiriting result 3
loop step ---------------- 4
isPrimeNumber(4) call
loop step ---------------- 5
isPrimeNumber(5) call
wiriting result 5
loop step ---------------- 6
isPrimeNumber(6) call
loop step ---------------- 7
isPrimeNumber(7) call
wiriting result 7
loop step ---------------- 8
isPrimeNumber(8) call
loop step ---------------- 9
isPrimeNumber(9) call
loop step ---------------- 10
isPrimeNumber(10) call
loop step ---------------- 11
isPrimeNumber(11) call

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

يبدو أنها قدرة مثيرة للاهتمام ولطيفة: د

هذه صورة ذهنية لما yield يفعل.

أحب أن أفكر في موضوع يحتوي على مكدس (حتى عندما لا يتم تنفيذه بهذه الطريقة).

عندما يتم استدعاء وظيفة طبيعية ، فإنها تضع متغيراتها المحلية على المكدس ، ويقوم ببعض الحسابات ، ثم يقوم بمسح المكدس ويعود. لا يتم رؤية قيم متغيراتها المحلية مرة أخرى.

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

لذلك فهو نوع من الوظائف المجمدة التي يتسكع فيها المولد.

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

قارن الأمثلة التالية:

def normalFunction():
    return
    if False:
        pass

def yielderFunction():
    return
    if False:
        yield 12

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

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

الدعوة yielderFunction() لا يعمل رمزه ، ولكنه يجعل المولد من الكود. (ربما من الجيد تسمية مثل هذه الأشياء مع yielder بادئة لقدرة القراءة.)

>>> 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']

ال gi_code و gi_frame الحقول هي المكان الذي يتم فيه تخزين الدولة المجمدة. استكشافهم dir(..), ، يمكننا أن نؤكد أن نموذجنا العقلي أعلاه موثوق به.

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

def getNextLines():
   while con.isOpen():
       yield con.read()

يمكنك استخدامه في الكود الخاص بك على النحو التالي:

for line in getNextLines():
    doSomeThing(line)

نقل التحكم في التنفيذ مسكتك

سيتم نقل التحكم في التنفيذ من getNextlines () إلى for حلقة عند تنفيذ العائد. وبالتالي ، في كل مرة يتم فيها التذرع في GetNextlines () ، يبدأ التنفيذ من النقطة التي تم إيقاف تشغيلها في المرة الأخيرة.

وهكذا باختصار ، وظيفة مع الكود التالي

def simpleYield():
    yield "first time"
    yield "second time"
    yield "third time"
    yield "Now some useful value {}".format(12)

for i in simpleYield():
    print i

سوف تطبع

"first time"
"second time"
"third time"
"Now some useful value 12"

العائد هو كائن

أ return في الوظيفة سوف تُرجع قيمة واحدة.

إذا أردت وظيفة لإرجاع مجموعة ضخمة من القيم, ، استعمال yield.

أكثر أهمية، yield هو حاجز.

مثل الحاجز في لغة CUDA ، لن ينقل التحكم حتى يكتمل.

أي أنه سيتم تشغيل الكود في وظيفتك من البداية حتى يضرب yield. ثم ، سوف يعيد القيمة الأولى للحلقة.

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

(إجابتي أدناه تتحدث فقط من منظور استخدام Python Generator ، وليس التنفيذ الأساسي لآلية المولد, والتي تنطوي على بعض الحيل من معالجة المكدس والمواد.)

متي yield يستخدم بدلا من أ return في وظيفة Python ، يتم تحويل هذه الوظيفة إلى شيء يسمى بشكل خاص generator function. ستؤدي هذه الوظيفة إلى إرجاع كائن generator يكتب. ال yield الكلمة الرئيسية هي علامة لإخطار برنامج التحويل البرمجي للبيثون بمعالجة هذه الوظيفة بشكل خاص. ستنتهي الوظائف العادية بمجرد إرجاع بعض القيمة منه. ولكن بمساعدة المترجم ، وظيفة المولد يمكن التفكير في كما يمكن استئناف. أي أنه سيتم استعادة سياق التنفيذ وسيستمر التنفيذ من التشغيل الأخير. حتى تقوم بالاتصال بشكل صريح ، والتي سترفع أ StopIteration استثناء (وهو أيضًا جزء من بروتوكول التكرار) ، أو الوصول إلى نهاية الوظيفة. لقد وجدت الكثير من المراجع حول generator لكن هذا واحد من functional programming perspective هو الأكثر قابلية للهضم.

(الآن أريد أن أتحدث عن الأساس المنطقي وراء generator, ، و ال iterator بناء على فهمي. آمل أن يساعدك هذا في فهم الدافع الأساسي من التكرار والمولد. يظهر مثل هذا المفهوم بلغات أخرى مثل C#.)

كما أفهم ، عندما نريد معالجة مجموعة من البيانات ، عادة ما نقوم أولاً بتخزين البيانات في مكان ما ثم نقوم بمعالجتها واحدة تلو الأخرى. لكن هذا ساذج النهج مشكلة. إذا كان حجم البيانات ضخمًا ، فمن مكلف تخزينها ككل مسبقًا. لذا بدلاً من تخزين data نفسها مباشرة ، لماذا لا تخزن نوعًا ما metadata بشكل غير مباشر ، أي the logic how the data is computed.

هناك نهجان لالتفاف مثل هذه البيانات الوصفية.

  1. نهج OO ، نلف البيانات الوصفية as a class. هذا هو ما يسمى iterator من الذي ينفذ بروتوكول التكرار (أي __next__(), ، و __iter__() طُرق). هذا هو أيضا شائع نمط تصميم التكرار.
  2. النهج الوظيفي ، نلف البيانات الوظيفية as a function. هذا هو ما يسمى generator function. ولكن تحت الغطاء ، عاد generator object ساكن IS-A التكرار لأنه ينفذ أيضًا بروتوكول التكرار.

في كلتا الحالتين ، يتم إنشاء التكرار ، أي بعض الكائنات التي يمكن أن تعطيك البيانات التي تريدها. قد يكون نهج OO معقدًا بعض الشيء. على أي حال ، أي واحد لاستخدامه متروك لك.

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

def simple_generator():
    yield 'one'
    yield 'two'
    yield 'three'

for i in simple_generator():
    print i

ببساطة المخرجات

one
two
three

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

قل أنك تريد إنشاء ملف خاص بك range الوظيفة التي تنتج مجموعة من الأرقام ، يمكنك القيام بذلك مثل ذلك ،

def myRangeNaive(i):
    n = 0
    range = []
    while n < i:
        range.append(n)
        n = n + 1
    return range

واستخدامه مثل هذا ؛

for i in myRangeNaive(10):
    print i

لكن هذا غير فعال بسبب

  • يمكنك إنشاء صفيف تستخدمه مرة واحدة فقط (هذه الذاكرة تضيع)
  • هذا الرمز في الواقع يحلق على هذا الصفيف مرتين! :(

لحسن الحظ ، كان Guido وفريقه كرمًا بما يكفي لتطوير المولدات حتى نتمكن من القيام بذلك ؛

def myRangeSmart(i):
    n = 0
    while n < i:
       yield n
       n = n + 1
    return

for i in myRangeSmart(10):
    print i

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

كثير من الناس يستخدمون return عوضا عن yield, ، ولكن في بعض الحالات yield يمكن أن تكون أكثر كفاءة وأسهل للعمل معها.

هنا مثال yield بالتأكيد الأفضل لـ:

إرجاع (فى مهمة)

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

أَثْمَر (فى مهمة)

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.

وظائف الاتصال

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)

كلتا الوظيفتين تفعل نفس الشيء ، ولكن yield يستخدم ثلاثة خطوط بدلاً من خمسة ولديه متغير أقل للقلق.

هذه هي النتيجة من الكود:

Output

كما ترون كلا الوظيفتين تفعل نفس الشيء. الفرق الوحيد هو return_dates() يعطي قائمة و yield_dates() يعطي مولد.

سيكون مثال الحياة الحقيقية مثل قراءة سطر الملف حسب السطر أو إذا كنت ترغب فقط في إنشاء مولد.

yield يشبه عنصر الإرجاع لوظيفة. الفرق هو أن yield العنصر يحول وظيفة إلى مولد. يتصرف المولد تمامًا مثل الوظيفة حتى يتم "العائد". يتوقف المولد حتى يتم استدعاؤه بعد ذلك ، ويستمر من نفس النقطة تمامًا التي بدأت بها. يمكنك الحصول على تسلسل لجميع القيم "المحفوظة" في واحدة ، عن طريق الاتصال list(generator()).

ال yield الكلمة الرئيسية ببساطة تجمع النتائج العائدة. افكر في yield مثل return +=

هذا بسيط yield النهج القائم ، لحساب سلسلة فيبوناتشي ، أوضح:

def fib(limit=50):
    a, b = 0, 1
    for i in range(limit):
       yield b
       a, b = b, a+b

عند إدخال هذا في REPL الخاص بك ثم حاول الاتصال به ، ستحصل على نتيجة محير:

>>> fib()
<generator object fib at 0x7fa38394e3b8>

هذا لأن وجود yield أشار إلى Python أنك تريد إنشاء ملف مولد كهرباء, ، أي كائن يولد القيم عند الطلب.

لذا ، كيف تولد هذه القيم؟ يمكن القيام بذلك مباشرة باستخدام الوظيفة المدمجة next, ، أو ، بشكل غير مباشر عن طريق إطعامه لبناء يستهلك القيم.

باستخدام المدمج next() وظيفة ، أنت تستدعي مباشرة .next/__next__, ، إجبار المولد على إنتاج قيمة:

>>> g = fib()
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5

بشكل غير مباشر ، إذا قدمت fib إلى for حلقة ، أ list التهيئة ، أ tuple التهيئة ، أو أي شيء آخر يتوقع كائنًا يولد/ينتج قيمًا ، ستستهلك "المولد" حتى لا يمكن إنتاج أي قيم أكثر (ويعود):

results = []
for i in fib(30):       # consumes fib
    results.append(i) 
# can also be accomplished with
results = list(fib(30)) # consumes fib

وبالمثل ، مع أ tuple التهيئة:

>>> tuple(fib(5))       # consumes fib
(1, 1, 2, 3, 5)

يختلف المولد عن وظيفة بمعنى أنها كسول. إنه يحقق ذلك من خلال الحفاظ على حالته المحلية والسماح لك بالاستئناف كلما احتجت إلى ذلك.

عندما تستدعي لأول مرة fib عن طريق تسميته:

f = fib()

Python يجمع الوظيفة ، ويواجه yield الكلمة الرئيسية وببساطة إرجاع كائن المولد عليك. ليس مفيدًا جدًا على ما يبدو.

عندما تطلب ذلك ، تقوم بإنشاء القيمة الأولى ، بشكل مباشر أو غير مباشر ، فإنها تنفذ جميع العبارات التي يجدها ، حتى تواجه أ yield, ، ثم يعطي القيمة التي قدمتها لها yield والتوقف. على سبيل المثال ، يوضح هذا بشكل أفضل ، دعنا نستخدم البعض print المكالمات (استبدل بـ print "text" إذا كان على بيثون 2):

def yielder(value):
    """ This is an infinite generator. Only use next on it """ 
    while 1:
        print("I'm going to generate the value for you")
        print("Then I'll pause for a while")
        yield value
        print("Let's go through it again.")

الآن ، أدخل في الاستبدال:

>>> gen = yielder("Hello, yield!")

لديك كائن مولد ينتظر الآن أمرًا لإنشاء قيمة. يستخدم next وشاهد ما يطبع Get:

>>> next(gen) # runs until it finds a yield
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

النتائج غير المؤهلة هي ما هو مطبوع. النتيجة المقتبسة هي ما تم إرجاعه yield. مكالمة next مرة أخرى الآن:

>>> next(gen) # continues from yield and runs again
Let's go through it again.
I'm going to generate the value for you
Then I'll pause for a while
'Hello, yield!'

يتذكر المولد أنه تم إيقافه مؤقتًا yield value ويستأنف من هناك. تتم طباعة الرسالة التالية والبحث عن yield بيان توقف في ذلك مرة أخرى (بسبب while عقدة).

An easy example to understand what it is: yield

def f123():
    for _ in range(4):
        yield 1
        yield 2


for i in f123():
    print i

The output is:

1 2 1 2 1 2 1 2
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top