generator هو دالة أو تعبير يُرجع نوعًا خاصًا من المكرّر يُسمى مكرّر المولّد.
Generator-iterators كسولة: فهي لا تخزّن values في الذاكرة، بل تولّد قيمها عند الحاجة.
دالة المولّد تبدو مثل أي دالة أخرى، لكنها تحتوي على تعبيرات yield واحدًا أو أكثر.
كل yield سيُعلّق تنفيذ الكود، ويحفظ حالة التنفيذ الحالية (بما في ذلك كل المتغيرات المحلية وعبارات try).
وعندما يستأنف المولّد عمله، يستكمل حالته من نقطة التعليق، على عكس الدوال العادية التي تُصفَّر مع كل استدعاء.
تُبنى المولّدات كثيرًا مثل غيرها من دوال الحلقات أو الدوال العودية، لكنها تحتاج إلى تعبير yield، وسنستكشفه بعمق بعد قليل.
ومن الأمثلة على ذلك دالة تُرجع مربّعات الأعداد من مصفوفة أعداد معطاة.
وكما هي مكتوبة الآن، يجب معالجة كل المدخلات قبل أن يُمكن إرجاع أي قيم:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
يمكنك تحويل تلك الدالة إلى مولّد هكذا:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
والمنطق وراء ذلك هو أنك تستخدم مولّدًا عندما لا تحتاج إلى كل القيم دفعة واحدة.
وهذا يوفّر الذاكرة وقوة المعالجة، لأن القيمة الوحيدة التي تعمل عليها حاليًا هي التي تُحسب.
المولّدات (من الناحية التقنية هي generator-iterators، انظر الملاحظة أدناه.) نوع من iterator، ويمكن استخدامها في أي مكان في Python حيث يُتوقع iterator أو iterable.
وهذا يشمل الدوال أو الكائنات التي تحتاج إلى iterable/iterator كوسيط.
وللتعمّق أكثر، انظر كيف تصنع مكرّرًا في Python.
مكرّرات المولّد هي مجموعة فرعية خاصة من المكرّرات.
Iterators هي الآلية أو البروتوكول الذي يتيح التكرار على القيم القابلة للتكرار.
مكرّرات المولّد والمكرّرات التي تُرجعها iterables الشائعة في Python تتصرف بشكل متشابه جدًا، لكن هناك بعض الفروق المهمة التي يجب ملاحظتها:
indexes.
فلا يمكنك الرجوع إلى قيمة سابقة أو لاحقة بالجمع أو الطرح، ولا يمكنك استخدام تدوين الأقواس المربعة ([]) أو التقطيع.len()، لأنها بلا طول.generator-iterator غير منتهٍ!لاستخدام المولّد squares_generator():
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
يمكن أيضًا إنتاج القيم داخل المولّد أو الوصول إليها عبر الدالة next().
تستدعي next() الطريقة __next__() في كائن المولّد، فتُقدّم أو تُقيّم كود المولّد حتى تعبير yield الخاص به، والذي «يولّد» أو يُرجع القيمة بعد ذلك.
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
وعندما يُستهلك generator بالكامل ولا تبقى لديه قيم يُرجعها، فإنه يرفع خطأ StopIteration.
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
المولّدات هي مجموعة فرعية خاصة من المكرّرات.
Iterators هي الآلية أو البروتوكول الذي يتيح التكرار على القيم القابلة للتكرار.
المولّدات والمكرّرات التي تُرجعها iterables الشائعة في Python تتصرف بشكل متشابه جدًا، لكن هناك بعض الفروق المهمة التي يجب ملاحظتها:
المولّدات أحادية الاتجاه؛ فلا يوجد «رجوع» إلى قيمة سابقة.
التكرار على المولّدات يستهلك القيم المُرجَعة؛ فلا تصفير.
المولّدات (لأنها تُقيَّم بتكاسل) غير قابلة للترتيب ولا يمكن عكسها.
المولّدات ليس لديها indexes، لذا لا يمكنك الرجوع إلى قيمة سابقة أو لاحقة بالجمع أو الطرح.
لا يمكن استخدام المولّدات مع الدالة len().
يمكن أن تكون المولّدات منتهية أو غير منتهية، فكن حذرًا عند جمع كل القيم من مولّد غير منتهٍ.
yield
تعبير yield شبيه جدًا بتعبير return.
و_على عكس_ تعبير return، فإن yield يتنازل عن القيم للمستدعي عند نقطة محددة، فيُعلّق تقييم أو إرجاع أي قيم إضافية حتى تُطلب.
وعندما يُقيَّم yield، فإنه يوقف تنفيذ الدالة الحاضنة ويُرجع قيم الدالة في تلك اللحظة من الزمن.
ثم تبقى الدالة في النطاق، وعندما تُستدعى __next__()، يستأنف التنفيذ حتى يُصادف yield مرة أخرى.
يُحظر استخدام تعبيرات yield خارج الدوال.
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
المولّدات مفيدة في كثير من التطبيقات.
عند العمل مع مجموعة كبيرة، قد لا ترغب في وضع كل قيمها في memory.
ويمكن استخدام مولّد للعمل على بيانات أكبر قطعة تلو الأخرى، مما يوفّر الذاكرة ويحسّن الأداء.
كما أن المولّدات مفيدة جدًا عندما تكون عملية أو حسابات ما معقدة أو مكلفة أو غير منتهية:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
والآن، كلما استُدعيت __next__() على الكائن infinite_sequence، فستُرجع العدد السابق + 1.