ال

المولّدات في Python

1 تمرين

نبذة عن المولّدات

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.

Note

مكرّرات المولّد هي مجموعة فرعية خاصة من المكرّرات. 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 مرة أخرى.

Note

يُحظر استخدام تعبيرات 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.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة

تعلّم المولّدات

التدريب مقفل

افتح تمرينين إضافيين لممارسة المولّدات