it-swarm.asia

ماذا تفعل كلمة "العائد"؟

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

9208
Alex. S.

لفهم ما يفعله yield ، يجب أن تفهم ما هي {المولدات. وقبل أن تتمكن من فهم المولدات ، يجب أن تفهم iterables.

Iterables

عند إنشاء قائمة ، يمكنك قراءة عناصرها واحدة تلو الأخرى. قراءة عناصرها واحد تلو الآخر يسمى التكرار:

>>> mylist = [1, 2, 3]
>>> for i in mylist:
...    print(i)
1
2
3

mylist هو iterable. عند استخدام فهم القائمة ، تقوم بإنشاء قائمة ، وبالتالي فهي قابلة للتكرار:

>>> 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 أن تكون قابلة للتكرار حتى تعمل مع السلاسل والقوائم والأنواع والمولدات! وهذا ما يسمى الكتابة البط وهو أحد أسباب بيثون هو بارد جدا. لكن هذه قصة أخرى ، لسؤال آخر ...

يمكنك التوقف هنا ، أو قراءة القليل لرؤية استخدام متقدم للمولد:

السيطرة على استنفاد مولد

>>> 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__()). Iterables هي أي كائنات يمكنك الحصول عليها من التكرار. التكرارات هي كائنات تسمح لك بالتكرار على التكرارات.

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

13362
e-satis

اختصار لفهم yield

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

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

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

لا تخلط بين الخاص بك Iterables ، المحولات والمولدات

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

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

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

  1. الحصول على مكرر لـ mylist:

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

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

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

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

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

هنا mylist هو iterable لأنه ينفذ بروتوكول التكرار. في فئة المعرفة من قبل المستخدم ، يمكنك تطبيق الأسلوب __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() ، فإنه لا إرجاع أي من القيم في عبارات العائد! تقوم بإرجاع كائن مولد. أيضًا ، لا تخرج الوظيفة فعليًا - فهي تذهب إلى حالة التعليق. عندما تحاول حلقة for التكرار فوق كائن المولد ، ستستأنف الوظيفة من حالتها الموقوفة في السطر التالي للغاية بعد yield التي أرجعت منها سابقًا ، وتنفذ السطر التالي من التعليمات البرمجية ، وفي هذه الحالة ، تُرجع عبارة yield وتُرجع ذلك كـ العنصر التالي. يحدث هذا حتى يتم إنهاء الوظيفة ، وعند هذه النقطة يرفع المولد StopIteration ، وتخرج الحلقة.

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

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

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

1786
user28409

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

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

نسخة أصلية:

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

for i in some_function():
    print i

هذا هو الأساس الذي يفعله مترجم Python بالشفرة أعلاه:

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

هل هذا أكثر منطقية أم مجرد إرباكك أكثر؟ :)

أود أن أشير إلى أن هذا هو تبسيط مفرط لأغراض التوضيح. :)

466
Jason Baker

يتم تقليل الكلمة الأساسية yield إلى حقيقتين بسيطتين:

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

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


تفصيلات

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

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

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

وبالتالي ، في حالة حدوث فشل محتمل في القيام بشيء مثل هذا ...

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

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

396
ninjagecko

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

الإجابة مخطط/ملخص

  • دالة مع yield ، عند الاتصال ، تُرجع Generator .
  • المولدات عبارة عن تكرارات لأنها تقوم بتطبيق بروتوكول التكرار ، حتى تتمكن من التكرار عليها.
  • يمكن أن يكون المولد أيضًا المعلومات المرسلة ، مما يجعله من الناحية النظرية a coroutine .
  • في Python 3 ، يمكنك المفوض من مولد إلى آخر في كلا الاتجاهين معyield from.
  • (ينتقد الملحق بعض الإجابات ، بما في ذلك أعلى واحد ، ويناقش استخدام return في المولد.)

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

yieldهو قانوني فقط من داخل تعريف الوظيفة ، و إدراج yield في تعريف دالة يجعله إرجاع منشئ.

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

يوفر yield طريقة سهلة لـ تطبيق بروتوكول التكرار ، المعرّفة من خلال الطريقتين التاليتين: __iter__ و nextPython 2) أو __next__ (Python 3). كلا الطريقتين تجعل الكائن مكررًا يمكنك كتابته- تحقق مع Iterator Class Base Abstract من الوحدة النمطية 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 الفرعية.

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

وفد تعاوني إلى Sub-Coroutine مع 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 في PEP 380.

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

يثير الأسلوب closeGeneratorExit عند نقطة تجميد تنفيذ الوظيفة. سيتم استدعاء هذا أيضًا بواسطة __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 يفعل الكثير. أنا متأكد من أنني يمكن أن أضيف أمثلة أكثر شمولا لهذا. إذا كنت تريد المزيد أو لديك بعض النقد البناء ، فأخبرني عن طريق التعليق أدناه.


الملحق:

نقد الأعلى/الإجابة المقبولة **

نقد الإجابة الذي يشير إلى 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 هم _ (يناقشون إلغاء بدلها . إليك منشورًا ذو صلة من القائمة البريدية:

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

في أحد ، 29 يناير 2017 في الساعة 16:39 كتب كريج رودريغز:

أنا موافق مع أي من النهج. ترك الأشياء كما هي في Python 3 ليس جيدًا ، IMHO.

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

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

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

  • SyntaxWarning أو DeprecationWarning في 3.7
  • تحذير Py3k في 2.7.x
  • SyntaxError في 3.8

هتاف ، نيك.

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

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

خلاصة القول ، حتى يخبرنا مطورو CPython بخلاف ذلك: لا تضع yield في تعبير أو استنباط منشئ.

عبارة return في منشئ

في بيثون 2 :

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

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

في بيثون 3 :

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

الحواشي

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

  2. هذا يعني ، على سبيل المثال ، أن الكائنات xrangerange في Python 3) ليست Iterators ، على الرغم من أنها قابلة للتكرار ، لأنه يمكن إعادة استخدامها. مثل القوائم ، تُرجع طرق __iter__ الخاصة بها كائنات متكررة.

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

313
Aaron Hall

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

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

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

270
Douglas Mayle

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

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

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

205
Claudiu

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

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

TL ؛ DR

بدلا من هذا:

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() على المكرر ( يحدث هذا في تنفيذ for-loop بوصة إلى الأمام إلى العائد التالي. return يثير StopIteration وينهي السلسلة (هذه هي النهاية الطبيعية للحلقة).

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

>>> 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 لأن كلا المعنيين تطبيق:

العائد - إنتاج أو توفير (كما هو الحال في الزراعة)

... تقديم البيانات التالية في السلسلة.

العائد - تفسح أو تتخلى (كما في السلطة السياسية)

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

172
Bob Stein

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

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 مليار في وقت واحد وتخزين الأرقام 50 مليار لحساب من خلال.

مرة أخرى ، هذا مثال رائع ، فمن المحتمل أن تستخدم أدوات itertools إذا أردت حقًا أن تصل إلى 50 مليار. :)

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

158
RBansal

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

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

152
Jon Skeet

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

إرجاع العبارة yield في Python مولد. مولد في بيثون هي وظيفة ترجع استمرارا (وبالتحديد نوع من 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 ، لأنه جحيم يسهل فهمه وقراءته.


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

def f():
  while True:
    yield 4

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

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

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

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

138
aestrivex

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

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

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

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

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

for item in sequence:

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

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

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

125
tzot

بينما توضح الكثير من الإجابات سبب استخدامك 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()
110
Mike McKerns

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

yield from <expr>

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

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

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

علاوة على ذلك هذا سيعرض (منذ بيثون 3.5):

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

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

102
Sławomir Lenart

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

أفترض أنك تعلمت بيان 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 هما توأمان
88
JawSaw

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

كمولد بيثون:

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)

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

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)
86
Dustin Getz

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

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

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

82
johnzachary

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

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

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

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

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

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

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

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

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

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

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

72
Engin OZTURK

في ما يلي صورة ذهنية لما يفعله 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(..) ، يمكننا أن نؤكد أن نموذجنا العقلي أعلاه ذو مصداقية.

59
Evgeni Sergeev

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

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

يمكنك استخدامه في التعليمات البرمجية الخاصة بك كما يلي:

for line in getNextLines():
    doSomeThing(line)

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

سيتم نقل عنصر تحكم التنفيذ من 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"
50
Mangu Singh Rajpurohit

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

44
smwikipedia

العائد كائن

سوف return في دالة بإرجاع قيمة واحدة.

إذا كنت تريد دالة لإرجاع مجموعة كبيرة من القيم ، استخدم yield.

الأهم من ذلك ، yield هو حاجز .

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

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

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

44
Kaleem Ullah

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

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

ولكن هذا غير فعال ل

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

لحسن الحظ جيدو وفريقه كانوا كرماء بما يكفي لتطوير المولدات الكهربائية حتى نتمكن من القيام بذلك ؛

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' مرة أخرى ، سوف تستمر بهذه الطريقة إلى أن تعود الحالة إلى الحالة الخاطئة وينتقل المولد إلى نهاية الوظيفة.

43
redbandit

يستخدم العديد من الأشخاص 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() يعطي مولد.

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

41
Tom Fuller

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

36
Theoremiser

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

35
Bahtiyar Özdere

إليك طريقة بسيطة تعتمد على 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 أنك تريد إنشاء generator ، أي كائن يولد قيمًا عند الطلب.

لذلك ، كيف يمكنك توليد هذه القيم؟ يمكن القيام بذلك مباشرة باستخدام الوظيفة المضمنة 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" إذا كان في Python 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.")

الآن ، أدخل في REPL:

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

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

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

32
Jim Fasarakis Hilliard

مثال بسيط على ما يتم شرحه بسهولة: yield

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


for i in f123():
    print i

الإخراج هو:

1 2 1 2 1 2 1 2
31
Gavriel Cohen

بعد TL آخر ؛ د

يقوم Iterator في القائمة : next() بإرجاع العنصر التالي من القائمة

مولد Iterator : next() سيحسب العنصر التالي على الطاير (رمز التنفيذ)

يمكنك رؤية العائد/المولد كوسيلة لتشغيل التحكم في التدفق من الخارج (مثل متابعة حلقة خطوة واحدة) ، عن طريق استدعاء next ، مهما كان التدفق معقدًا.

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

30
Christophe Roussy

العائد يشبه العودة. والفرق هو:

yield يجعل دالة قابلة للتكرار (في المثال التالي ، تصبح الدالة primes(n = 1) قابلة للتكرار).
ما يعنيه بشكل أساسي هو في المرة القادمة التي تسمى فيها الوظيفة ، وسوف تستمر من حيث تركت (والتي هي بعد سطر yield expression).

def isprime(n):
    if n == 1:
        return False
    for x in range(2, n):
        if n % x == 0:
            return False
    else:
        return True

def primes(n = 1):
   while(True):
       if isprime(n): yield n
       n += 1 

for n in primes():
    if n > 100: break
    print(n)

في المثال أعلاه ، إذا كانت الدالة isprime(n) صحيحة ، فسوف تُرجع الرقم الأولي. في التكرار التالي ، سيستمر من السطر التالي

n += 1  
24
blueray

كل الإجابات هنا رائعة ؛ لكن واحدًا منهم (الأكثر صوتًا واحدًا) يتعلق بـ كيفية عمل الكود الخاص بك. البعض الآخر يتعلق بـ المولدات بشكل عام ، وكيفية عملها.

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

رمزك يعبر بنية شجرة ثنائية. لنأخذ هذه الشجرة على سبيل المثال:

    5
   / \
  3   6
 / \   \
1   4   8

وتنفيذ آخر أكثر بساطة لاجتياز شجرة البحث الثنائي:

class Node(object):
..
def __iter__(self):
    if self.has_left_child():
        for child in self.left:
            yield child

    yield self.val

    if self.has_right_child():
        for child in self.right:
            yield child

رمز التنفيذ موجود على كائن Tree ، والذي يقوم بتنفيذ __iter__ كما يلي:

def __iter__(self):

    class EmptyIter():
        def next(self):
            raise StopIteration

    if self.root:
        return self.root.__iter__()
    return EmptyIter()

يمكن استبدال عبارة while candidates بـ for element in tree ؛ ترجمة بيثون هذا ل

it = iter(TreeObj)  # returns iter(self.root) which calls self.root.__iter__()
for element in it: 
    .. process element .. 

نظرًا لأن دالة Node.__iter__ هي منشئ ، يتم تنفيذ الكود بداخلها لكل تكرار. لذا فإن التنفيذ سيبدو كما يلي:

  1. العنصر الجذر هو الأول ؛ تحقق مما إذا كان قد ترك تشايلدز و for يكررها (دعنا نسميها 1 لأنها أول كائن تكراري)
  2. لديها طفل بحيث يتم تنفيذ for. ينشئ for child in self.leftمكرر جديد من self.left ، وهو كائن عقدة نفسه (it2)
  3. نفس المنطق مثل 2 ، ويتم إنشاء iterator جديد (it3)
  4. الآن وصلنا إلى الطرف الأيسر من الشجرة. it3 لا يوجد لديه طفل اليسار لذلك يستمر و yield self.value
  5. في المكالمة التالية إلى next(it3) ، ترفع StopIteration وتوجد نظرًا لعدم وجود تشايلدرات مناسبة لها (تصل إلى نهاية الوظيفة دون أن تسفر عن أي شيء)
  6. it1 و it2 لا يزالان نشيطين - لم يتم استنفادهما ، وسيؤدي استدعاء next(it2) إلى تحقيق قيم وليس رفع StopIteration
  7. لقد عدنا الآن إلى سياق it2 ، وندعو next(it2) التي تستمر حيث توقفت: بعد عبارة yield child مباشرة. نظرًا لأنه لم يعد هناك عدد أكبر من الأطفال ، فإنه يستمر ويؤدي إلى self.val.

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

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

آمل أن يكون هذا قد ساهم قليلاً في هذا الموضوع الأسطوري. قضيت عدة ساعات جيدة في رسم هذه العملية لفهمها.

11
Chen A.

باختصار ، يشبه استخدام العائد الكلمة الأساسية العائد ، إلا أنه يُرجع المولد .
A المولد كائن يجتاز فقط مرة واحدة .

العائد له فائدتان:

  1. لا تحتاج إلى قراءة هذه القيم مرتين ؛
  2. يمكنك الحصول على العديد من العقد التابعة دون وضعها في الذاكرة.
8
123

في Python generators (نوع خاص من iterators) يُستخدم لإنشاء سلسلة من القيم والكلمة الرئيسية yield تمامًا مثل الكلمة الرئيسية return لوظائف المولد.

والكلمة الأخرى الرائعة yield هي حفظ state لوظيفة المولد .

لذلك ، يمكننا ضبط number على قيمة مختلفة في كل مرة ينتج عنها generator.

إليك مثال:

def getPrimes(number):
    while True:
        if isPrime(number):
            number = yield number     # a miracle occurs here
        number += 1

def printSuccessivePrimes(iterations, base=10):
primeGenerator = getPrimes(base)
primeGenerator.send(None)
for power in range(iterations):
    print(primeGenerator.send(base ** power))
8
ARGeo

يخضع أو يستسلم

>>> def create_generator():
...    my_list = range(3)
...    for i in my_list:
...        yield i*i
...
>>> my_generator = create_generator() # create a generator
>>> print(my_generator) # my_generator is an object!
<generator object create_generator at 0xb7555c34>
>>> for i in my_generator:
...     print(i)
0
1
4

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

6
Gavriel Cohen

يمكن أن يساعد القياس في فهم الفكرة هنا:

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

لا تختلف مولدات Python كثيرًا عن هذا المفهوم.

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

كود الآلة:

def barcode_generator():
    serial_number = 10000  # Initial barcode
    while True:
        yield serial_number
        serial_number += 1


barcode = barcode_generator()
while True:
    number_of_lightbulbs_to_generate = int(input("How many lightbulbs to generate? "))
    barcodes = [next(barcode) for _ in range(number_of_lightbulbs_to_generate)]
    print(barcodes)

    # function_to_create_the_next_batch_of_lightbulbs(barcodes)

    produce_more = input("Produce more? [Y/n]: ")
    if produce_more == "n":
        break

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

انتاج:

How many lightbulbs to generate? 5
[10000, 10001, 10002, 10003, 10004]
Produce more? [Y/n]: y
How many lightbulbs to generate? 6
[10005, 10006, 10007, 10008, 10009, 10010]
Produce more? [Y/n]: y
How many lightbulbs to generate? 7
[10011, 10012, 10013, 10014, 10015, 10016, 10017]
Produce more? [Y/n]: n
6
Rafael

yield هو نوع من المولدات التي يمكن استخدامها في بيثون.

هنا رابط لنرى ما الذي يقوم به Yield فعلاً ، أيضًا في الجيل. المولدات و محصول الكلمة الرئيسية - Python Central (PC)

أيضًا yield يعمل مثل return ، لكن بطريقة مختلفة عن return. حتى أن هناك رابطًا يفسر yield أكثر ، إذا لم تفهم الآخر بشكل غير جيد. تحسين مهاراتك في الغلة - jeffknupp

3
PIZZZZZZZZZZZA is here

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

In [4]: def make_cake(numbers):
   ...:     for i in range(numbers):
   ...:         yield 'Cake {}'.format(i)
   ...:

In [5]: factory = make_cake(5)

هنا factory يسمى المولد ، مما يجعلك كعك. إذا اتصلت بـ make_function ، فستحصل على مولد بدلاً من تشغيل هذه الوظيفة. فذلك لأنه عندما تكون كلمة yield موجودة في دالة ما ، فإنها تصبح مولدًا.

In [7]: next(factory)
Out[7]: 'Cake 0'

In [8]: next(factory)
Out[8]: 'Cake 1'

In [9]: next(factory)
Out[9]: 'Cake 2'

In [10]: next(factory)
Out[10]: 'Cake 3'

In [11]: next(factory)
Out[11]: 'Cake 4'

لقد استهلكوا جميع الكعك ، لكنهم طلبوا مرة أخرى.

In [12]: next(factory)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-12-0f5c45da9774> in <module>
----> 1 next(factory)

StopIteration:

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

In [13]: factory = make_cake(3)

In [14]: for cake in factory:
    ...:     print(cake)
    ...:
Cake 0
Cake 1
Cake 2

يمكنك أيضًا استخدام الحلقة مع مولد مثل الموجود أعلاه.

مثال آخر: لنفترض أنك تريد كلمة مرور عشوائية كلما طلبت ذلك.

In [22]: import random

In [23]: import string

In [24]: def random_password_generator():
    ...:     while True:
    ...:         yield ''.join([random.choice(string.ascii_letters) for _ in range(8)])
    ...:

In [25]: rpg = random_password_generator()

In [26]: for i in range(3):
    ...:     print(next(rpg))
    ...:
FXpUBhhH
DdUDHoHn
dvtebEqG

In [27]: next(rpg)
Out[27]: 'mJbYRMNo'

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

2
thavan

وظيفة مولد بسيط

def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

يؤدي بيان العائد مؤقتًا إلى توفير وظيفة حفظ جميع حالاته ويستمر لاحقًا من هناك على المكالمات المتتالية.

https://www.programiz.com/python-programming/generator

1
Savai Maheshwari

في أبسط الكلمات ، يشبه "العائد" القيمة "المرتجعة" ، لكنه يعمل على المولد.

1
user3701435

في الغلة البسيطة تقوم بإرجاع كائن المولد بدلاً من القيم.

أدناه مثال بسيط سوف يساعد!

def sim_generator():
    for i in range(3):
        yield(i)

obj = sim_generator()
next(obj) # another way is obj.__next__()
next(obj)
next(obj)

إرجاع الرمز أعلاه 0 ، 1 ، 2

أو حتى قصيرة

for val in sim_generator():
    print(val)

عودة 0 ، 1 ، 2

أتمنى أن يساعدك هذا

1
Vivek Ananthan