python - ماذا تفعل الكلمة الرئيسية "العائد"؟




iterator generator (20)

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

على سبيل المثال ، أحاول فهم هذا الرمز 1 :

def _get_child_candidates(self, distance, min_dist, max_dist):
    if self._leftchild and distance - max_dist < self._median:
        yield self._leftchild
    if self._rightchild and distance + max_dist >= self._median:
        yield self._rightchild  

وهذا هو المتصل:

result, candidates = [], [self]
while candidates:
    node = candidates.pop()
    distance = node._get_dist(obj)
    if distance <= max_dist and distance >= min_dist:
        result.extend(node._values)
    candidates.extend(node._get_child_candidates(distance, min_dist, max_dist))
return result

ماذا يحدث عندما يتم استدعاء الطريقة _get_child_candidates ؟ هل تم إرجاع قائمة؟ عنصر واحد؟ هل تم الاتصال مرة أخرى؟ متى تتوقف المكالمات اللاحقة؟

1. رمز يأتي من Jochen شولز (jrschulz) ، الذي جعل مكتبة بيثون كبيرة للمساحات مترية. هذا هو الرابط إلى المصدر الكامل: وحدة mspace .


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

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

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

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

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

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

يوفر yield طريقة سهلة لتنفيذ بروتوكول التكرار ، المحدد __iter__ : __iter__ next (Python 2) أو __next__ (Python 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 أو __next__ ، وفقًا لإصدار Python الذي تستخدمه:

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

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

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

وفد تعاوني ل Sub-Coroutine مع yield from

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

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 الدقيقة yield from PEP 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 يقوم بالكثير. أنا متأكد من أنني يمكن أن أضيف المزيد من الأمثلة الدقيقة على ذلك. إذا كنت تريد المزيد أو لديك بعض النقد البناء ، فيرجى إبلاغي بالتعليق أدناه.

الملحق:

نقد أعلى / رد مقبول **

  • إنه مرتبك على ما يجعل التكرار ، فقط باستخدام قائمة كمثال. انظر المراجع أعلاه ، ولكن باختصار: يمكن إعادة التكرار __iter__ طريقة __iter__ . يوفر مكرر a .next (Python 2 أو .__next__ (Python 3) الطريقة ، التي يتم استدعاؤها ضمنيًا بواسطة الحلقات حتى ترفع StopIteration ، وبمجرد أن تقوم بذلك ، ستستمر في القيام بذلك.
  • ثم يستخدم تعبير مولد لوصف ما هو مولد. نظرًا لأن المولد هو ببساطة وسيلة ملائمة لإنشاء مُكرِّر ، إلا أنه يخلط الأمر فقط ، وما زلنا لم نصل بعد إلى جزء yield .
  • في السيطرة على استنفاد مولد يستدعي أسلوب .next ، عندما بدلا من ذلك يجب عليه استخدام وظيفة .next ، بعد ذلك. ستكون طبقة ملائمة من الاستبعاد ، لأن شفرته لا تعمل في Python 3.
  • Itertools؟ لم يكن هذا ذو صلة بما يفعله yield على الإطلاق.
  • لا توجد مناقشة للأساليب التي yield جنبا إلى جنب مع yield from الوظيفي الجديد في Python 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 الأساسيون استبعاد مخصصاتها. هذه رسالة ذات صلة من القائمة البريدية:

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

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

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

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

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

من حيث الوصول إلى هناك ، سنحتاج على الأرجح إلى:

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

ابتهاج ، نيك.

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

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

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

و returnجاء في مولد

في Python 2 :

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

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

في Python 3 :

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

الحواشي

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

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

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


اختصار Grokking

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

def f123():
    yield 1
    yield 2
    yield 3

for item in f123():
    print item

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

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

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

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


لفهم ماهية yield ، يجب أن تفهم ما هي المولدات . وقبل أن تأتي المولدات الكهربائية.

Iterables

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

>>> 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 ، ثم for i in mygenerator وتحسب 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 ، يجب أن تفهم أنه عند استدعاء الوظيفة ، لا يعمل التعليمة البرمجية التي كتبتها في نص الدالة. الدالة تقوم بإرجاع كائن المولد فقط ، وهذا قليلاً صعبة :-)

بعد ذلك ، سيتم تشغيل التعليمات البرمجية الخاصة بك في كل مرة تستخدم فيها المولد.

الآن الجزء الصعب:

في المرة الأولى التي يتم فيها استدعاء كائن المولد الذي تم إنشاؤه من وظيفتك ، سيقوم بتشغيل الكود في وظيفتك من البداية حتى تصل إلى 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 candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) مولدة جديدة ستنتج قيمًا مختلفة عن القيم السابقة حيث لا يتم تطبيقها على نفس المولدات. العقدة.

  • يعد أسلوب extend() طريقة كائن قائمة تتوقع توقعًا وتضيف قيمها إلى القائمة.

عادة ما نمرر قائمة بها:

>>> a = [1, 2]
>>> b = [3, 4]
>>> a.extend(b)
>>> print(a)
[1, 2, 3, 4]

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

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

وهو يعمل لأن بايثون لا تهتم إذا كانت حجة الأسلوب قائمة أم لا. بيثون تتوقع iterables لذلك سوف تعمل مع السلاسل والقوائم ، tuples والمولدات! يدعى هذا بطبع الكتابة وهو أحد الأسباب التي تجعل بايثون رائعة. ولكن هذه قصة أخرى ، لسؤال آخر ...

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

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

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

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


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

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

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

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

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

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

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

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

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

مثال

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

خلف الكواليس

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

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

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

تفصيلات

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

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

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

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

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

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


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

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

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


TL، DR

بدلا من هذا:

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

افعل هذا:

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

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

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

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

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

نفس السلوك:

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

سلوك مختلف:

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

العائد هو كسول ، فإنه يعطل الحساب. لا يتم تنفيذ أي دالة ذات عائد في الواقع عند استدعاءها. يستدعي كائن المكود الذي يعيده استخدام magic للحفاظ على السياق الداخلي للوظيفة. في كل مرة تقوم فيها باستدعاء next()مكرر (يحدث ذلك في حلقة for) تنفيذ البوصات إلى الأمام إلى العائد التالي. ( 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(squares_the_yield_way(4))
[0, 1, 4, 9]

اختيار رائع للكلمة yieldلأن المعانيتين تنطبقان:

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

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

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

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


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

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

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


بعد TL أخرى ؛ DR

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

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

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

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


فيما يلي بعض الأمثلة على كيفية تنفيذ المولدات فعليًا كما لو لم تقدم 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)

استخدام إغلاق الكائن بدلاً من المولدات (لأن 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)

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

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

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


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

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

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

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

هناك طريقتان لتغليف مثل هذه البيانات الوصفية.

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

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


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

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


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

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

كثير من الناس يستخدمون 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تستخدمان ثلاثة أسطر بدلاً من خمسة ولديهما متغير واحد أقل للقلق.

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

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

مثال الحياة الواقعي سيكون شيئًا مثل قراءة ملف خطًا سطراً ، أو إذا كنت تريد فقط إنشاء مولد.


مثل كل إجابة تقترح ، 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"

من وجهة نظر البرمجة ، يتم تنفيذ 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
->

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

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

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

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

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

for item in sequence:

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

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

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


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

yield from <expr>

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

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

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

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

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

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


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

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

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

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

في استمرار نمط التمرير (CPS) ، تكون الاستمرارية وظائف عادية فقط (فقط في اللغات التي تكون فيها الدوال من الدرجة الأولى) والتي يديرها المبرمج بشكل صريح ويمرر إلى subroutines. في هذا النمط ، يتم تمثيل حالة البرنامج بواسطة عمليات الإغلاق (والمتغيرات التي يتم تشفيرها بها) بدلاً من المتغيرات الموجودة في مكان ما على المكدس. إن الوظائف التي تدير تدفق التحكم تقبل الاستمرارية كحجج (في بعض اختلافات 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-pseudocode (أي ، ليس pseudocode ، لكن ليس رمز) ، تكون 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:]))

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


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





coroutine