Kodokon kodokon.com

المكرِّرات والمولّدات: yield وخطوط المعالجة

أتقن بروتوكول التكرار والمولّدات لمعالجة تدفقات البيانات معالجة كسولة، وبذاكرة ثابتة.

10 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

ليست حلقة for في Python سوى بروتوكول: تطلب iter(obj) مكرِّرًا، ثم يُستدعى next() حتى يُطلَق الاستثناء StopIteration. واحرص على التمييز بين الكائن القابل للتكرار (القائمة، القادرة على تسليم مكرِّر جديد في كل مرور) والمكرِّر (المؤشّر، الذي لا يمكن اجتيازه إلا مرة واحدة). وفهم هذا البروتوكول يعني فهم سبب "استهلاك" بعض الكائنات.

PYTHON
numbers = [10, 20, 30]
iterator = iter(numbers)
print(next(iterator))
print(next(iterator))
print(next(iterator))
# next(iterator) would raise StopIteration
ما تفعله حلقة for، يدويًا

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

PYTHON
def squares(limit):
    n = 0
    while n < limit:
        yield n * n
        n += 1

gen = squares(4)
print(next(gen))  # 0
print(list(gen))  # [1, 4, 9]
تُعلِّق yield الدالة بين قيمتين

الفائدة الرئيسية في الواقع العملي: خطوط المعالجة. تستهلك كل مرحلة تدفق المرحلة السابقة وتنتج تدفقها الخاص، بكسل؛ ولا شيء يُنفَّذ قبل الاستهلاك النهائي (list()، أو sum()، أو حلقة for). ومقايضة يجب تذكّرها: القائمة يمكن اجتيازها عدة مرات وتقطيعها، والمولّد لا؛ في المقابل، يعالج المولّد ملفًا بحجم 10 غيغابايت دون إشباع الذاكرة الحية. أما تعبيرات المولّدات - (f(x) for x in xs if cond) - فتغطي المراحل البسيطة دون تعريف دالة.

PYTHON
lines = ["  12 ", "x", " 7", "", "30"]

def clean(rows):
    for row in rows:
        row = row.strip()
        if row.isdigit():
            yield int(row)

def keep_from(values, floor):
    return (v for v in values if v >= floor)

pipeline = keep_from(clean(lines), 10)
print(list(pipeline))  # [12, 30]
خط معالجة كسول: لا شيء يُنفَّذ قبل list()

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ماذا تحصل عليه عندما تستدعي دالة يحتوي جسمها على yield؟
    • نتيجة أول yield
    • مولِّدًا، دون تنفيذ الجسم
    • قائمة بكل القيم المُنتَجة
  2. ما الميزة الحاسمة لخط معالجة من المولّدات مقارنة بالقوائم الوسيطة؟
    • تُرتَّب النتائج تلقائيًا
    • تبقى الذاكرة ثابتة: تُنتَج القيم عند الطلب
    • تُوزَّع المعالجة على عدة أنوية بالتوازي
  3. بعد أول list(gen)، ماذا يُرجِع list(gen) ثانٍ على المولِّد نفسه؟
    • القائمة نفسها التي أرجعها الاستدعاء الأول
    • قائمة فارغة
    • يُنشَر استثناء