مسار بايثون· الخطوة 18 من 19
المولّدات في بايثون: دالة تعطيك القيم واحدة واحدة
تعلّم المولّدات في بايثون: ما الفرق بين yield و return، وكيف تمرّ على المولّد بحلقة for، وتستخدم next()، وتوقف مولّدًا لا ينتهي بـ break.
تخيّل أنك في طابور آلة التذاكر في مدينة الألعاب. الآلة لا ترمي عليك كل التذاكر دفعة واحدة، بل تعطيك تذكرة، ثم تنتظر، ثم تعطي التالي تذكرته. في لغة بايثون توجد دالة تعمل بهذه الطريقة بالضبط، وتسمّى المولّد. في هذا الدرس من دروس تعلم بايثون ستكتب مولّدك الأول بنفسك.
🎯 ماذا ستتعلم؟
بنهاية هذا الدرس ستكون قادرًا على:
- شرح الفرق بين الدالة العادية والمولّد.
- كتابة مولّد باستخدام الكلمة
yield. - المرور على قيم المولّد بحلقة
forأو بالدالةnext(). - كتابة مولّد لا ينتهي وإيقافه بـ
break. - معرفة متى يوفّر المولّد الذاكرة مقارنة بالقائمة.
🧰 ماذا تحتاج قبل أن تبدأ؟
يجب أن تعرف كيف تكتب دالة بـ def وتُرجع قيمة بـ return، وقد شرحنا ذلك في الدوال في بايثون. وتحتاج أيضًا إلى الحلقات والقوائم. كل أكواد بايثون في هذا الدرس تعمل في مترجم بايثون في المتصفح، وهو محرر بايثون اون لاين بالعربي لا يحتاج إلى تحميل بايثون 3.
الفكرة ببساطة
الدالة العادية تشبه صديقًا تطلب منه قائمة الأغاني، فيكتبها كلها في ورقة ويعطيك الورقة مرة واحدة. بعد أن يعطيك الورقة ينتهي عمله.
أما المولّد (generator) فيشبه آلة التذاكر. تطلب منه قيمة، فيعطيك قيمة واحدة ثم يتوقف وينتظر. تطلب مرة أخرى، فيكمل من حيث توقّف ويعطيك القيمة التالية. وهكذا حتى تنتهي القيم.
الكلمة السحرية التي تصنع هذا التوقف هي yield. حين تراها داخل دالة، اعرف فورًا أن هذه الدالة مولّد.
الخطوة 1: من return إلى yield
لنبدأ بدالة عادية تُرجع قائمة بعدّ تنازلي لبداية سباق:
def countdown_list():
return [3, 2, 1]
for number in countdown_list():
print(number)
3
2
1
الدالة صنعت القائمة كاملة ثم أعادتها دفعة واحدة. الآن نكتب الشيء نفسه بمولّد. لاحظ أننا استبدلنا return بـ yield، وأن yield تظهر ثلاث مرات:
def countdown():
yield 3
yield 2
yield 1
for number in countdown():
print(number)
3
2
1
النتيجة نفسها، لكن ما يحدث في الداخل مختلف. في كل دورة من الحلقة، يعمل المولّد حتى يصل إلى yield، يعطي القيمة، ثم يتوقف. في الدورة التالية يكمل من السطر الذي بعدها.
الخطوة 2: yield داخل حلقة
كتابة yield ثلاث مرات ليست عملية. الأجمل أن نضعها داخل حلقة. هذا مولّد يعطيك جدول ضرب أي رقم:
def times_table(n):
for i in range(1, 6):
yield n * i
for value in times_table(7):
print(value)
7
14
21
28
35
في كل دورة من حلقة for الداخلية، يعطي المولّد ناتجًا واحدًا ويتوقف. الحلقة الخارجية تستقبل الناتج وتطبعه، ثم تطلب التالي. هذه أهم فكرة في برمجة بايثون بالمولّدات: العمل يتم قيمة بقيمة.
الخطوة 3: اطلب القيم بنفسك بـ next()
حلقة for تطلب القيم عنك. لكن يمكنك أن تطلبها بيدك بالدالة next(). سنضع أسطر طباعة بين كل yield لنرى التوقف بأعيننا:
def steps():
print("بدأ المولّد")
yield "الخطوة الأولى"
print("عاد المولّد للعمل")
yield "الخطوة الثانية"
gen = steps()
print(next(gen))
print("نحن خارج المولّد الآن")
print(next(gen))
بدأ المولّد
الخطوة الأولى
نحن خارج المولّد الآن
عاد المولّد للعمل
الخطوة الثانية
اقرأ الناتج بهدوء. أول next() شغّل المولّد حتى أول yield فقط. ثم طبعنا جملة من خارج المولّد. وثاني next() أكمل من حيث توقّف، لا من البداية. المولّد يتذكّر مكانه دائمًا.
🚀 للمتقدمين: السطر
gen = steps()لا يشغّل الدالة أبدًا، بل يصنع كائن المولّد فقط. لهذا لم تُطبع “بدأ المولّد” إلا عند أولnext(). جرّبprint(type(gen))وسترى<class 'generator'>.
الخطوة 4: مولّد لا ينتهي
هنا تظهر قوة المولّدات الحقيقية. يمكنك كتابة مولّد يعطي قيمًا إلى الأبد، لأنه لا يصنع كل القيم مسبقًا. هذا عدّاد يبدأ من 1 ولا يتوقف أبدًا:
def counter():
n = 1
while True:
yield n
n = n + 1
for number in counter():
print(number)
if number == 5:
break
1
2
3
4
5
الحلقة while True داخل المولّد لا تنتهي، لكن هذا لا يعلّق البرنامج. لماذا؟ لأن المولّد يعطي رقمًا واحدًا ثم يتوقف وينتظر. ونحن نوقف الطلب بـ break عندما نصل إلى 5.
لو حاولت كتابة هذا بدالة عادية تُرجع قائمة بكل الأرقام، لما انتهى البرنامج أبدًا. وهذا ما يميّز المولّد بين أوامر لغة بايثون: هو يعطي ما تطلبه فقط.
الخطوة 5: لماذا يوفّر المولّد الذاكرة؟
عندما تصنع قائمة بمليون رقم، يحجز برنامج بايثون مكانًا في الذاكرة لكل رقم منها. أما المولّد فيحفظ رقمًا واحدًا فقط في كل لحظة. لنجمع مربعات الأرقام من 1 إلى 10 بالطريقتين:
def squares(limit):
for i in range(1, limit + 1):
yield i * i
numbers_list = [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
print(sum(numbers_list))
print(sum(squares(10)))
385
385
الناتج واحد. لكن القائمة حفظت عشرة أرقام في الذاكرة دفعة واحدة، بينما المولّد أعطى sum() رقمًا رقمًا. مع عشرة أرقام لا فرق يُذكر، لكن مع ملايين الأرقام يصبح الفرق كبيرًا جدًا.
🚀 للمتقدمين: يمكنك كتابة مولّد صغير في سطر واحد بدون
def، ويسمّى تعبير المولّد (generator expression). اكتبه بين قوسين عاديين هكذا:sum(x * x for x in range(1, 11)). يعطيك 385 أيضًا. لو استبدلت القوسين بقوسين مربّعين، صنعت قائمة كاملة بدل مولّد.
⚠️ أخطاء شائعة
هذه الأخطاء يقع فيها كل من يبدأ تعلم لغة بايثون بالمولّدات، فلا تقلق إن وقعت فيها.
1. طباعة المولّد نفسه بدل قيمه
❌ لا يطبع القيم
def countdown():
yield 3
yield 2
yield 1
print(countdown())
<generator object countdown at 0x7f...>
هذا ليس خطأ يوقف البرنامج، لكنه لا يعطيك ما تريد. طبعت كائن المولّد لا القيم. لرؤية القيم، مرّ عليه بحلقة، أو حوّله إلى قائمة بـ list():
# ✅ نحوّل المولّد إلى قائمة
def countdown():
yield 3
yield 2
yield 1
print(list(countdown()))
[3, 2, 1]
2. طلب قيمة بعد انتهاء المولّد
❌ هذا الكود يتوقف بخطأ
def countdown():
yield 2
yield 1
gen = countdown()
print(next(gen))
print(next(gen))
print(next(gen))
2
1
StopIteration
المولّد أعطى قيمتين وانتهى، فالطلب الثالث يسبّب خطأ StopIteration. الحل الآمن هو حلقة for، لأنها تعرف متى تتوقف بنفسها:
# ✅ حلقة for تتوقف وحدها
def countdown():
yield 2
yield 1
for number in countdown():
print(number)
2
1
3. استخدام return بدل yield
❌ يعطي القيمة الأولى فقط
def countdown():
for n in [3, 2, 1]:
return n
print(countdown())
3
الكلمة return تنهي الدالة فورًا في أول دورة. أما yield فتعطي القيمة وتتوقف مؤقتًا ثم تكمل:
# ✅ yield تكمل بعد كل قيمة
def countdown():
for n in [3, 2, 1]:
yield n
for number in countdown():
print(number)
3
2
1
🧪 جرّب بنفسك
افتح محرر بايثون اون لاين وجرّب التحديات التالية بترتيبها. كل تحدٍّ أصعب قليلًا من الذي قبله.
التحدي 1: الأرقام الزوجية
اكتب مولّدًا اسمه evens يعطي الأرقام الزوجية من 2 إلى 10. ثم اطبعها بحلقة for.
def evens():
# أكمل هنا: استخدم range مع خطوة 2 ثم yield
yield 2
for number in evens():
print(number)
2
💡 اضغط لرؤية الحل
def evens():
for n in range(2, 11, 2):
yield n
for number in evens():
print(number)
2
4
6
8
10
التحدي 2: متسلسلة فيبوناتشي
في هذه المتسلسلة يكون أول رقمين 1 و 1، وكل رقم بعدهما هو مجموع الرقمين قبله. اكتب مولّدًا لا ينتهي اسمه fib، ثم اطبع أول 10 أرقام وأوقفه بـ break. تلميح: السطر a, b = b, a + b يبدّل القيمتين في خطوة واحدة.
def fib():
a = 1
b = 1
# أكمل هنا: حلقة لا تنتهي مع yield
yield a
for number in fib():
print(number)
1
💡 اضغط لرؤية الحل
def fib():
a = 1
b = 1
while True:
yield a
a, b = b, a + b
count = 0
for number in fib():
print(number)
count = count + 1
if count == 10:
break
1
1
2
3
5
8
13
21
34
55
التحدي 3: قائمة اللاعبين المرقّمة
عندك قائمة بأسماء اللاعبين. اكتب مولّدًا اسمه numbered يأخذ القائمة ويعطي كل اسم مع رقمه، مثل “1. سالم”. استخدم len() و range() للحصول على الفهرس.
players = ["سالم", "نورة", "خالد"]
def numbered(names):
# أكمل هنا
yield "1. " + names[0]
for line in numbered(players):
print(line)
1. سالم
💡 اضغط لرؤية الحل
players = ["سالم", "نورة", "خالد"]
def numbered(names):
for i in range(len(names)):
yield str(i + 1) + ". " + names[i]
for line in numbered(players):
print(line)
1. سالم
2. نورة
3. خالد
✅ اختبر نفسك
-
أي كلمة تجعل الدالة مولّدًا؟
- أ)
return - ب)
yield - ج)
break
- أ)
-
ماذا يحدث عند استدعاء
next()على مولّد انتهت قيمه؟- أ) يعيد
None - ب) يبدأ من جديد
- ج) يحدث خطأ
StopIteration
- أ) يعيد
-
صح أم خطأ: المولّد يصنع كل قيمه في الذاكرة قبل أن يعطيك أول قيمة.
💡 اضغط لرؤية الإجابات
- ب)
yield. وجودها داخل الدالة يجعلها مولّدًا. - ج) يحدث خطأ
StopIteration، ولهذا تُفضَّل حلقةforالتي تتوقف وحدها. - خطأ. المولّد يصنع قيمة واحدة في كل مرة تطلبها، وهذا سرّ توفيره للذاكرة.
📝 الخلاصة
- المولّد دالة تعطيك القيم واحدة واحدة وتتوقف بين كل قيمة والتي بعدها.
- الكلمة
yieldتعطي القيمة وتحفظ مكان التوقف، بينماreturnتنهي الدالة نهائيًا. - تمرّ على المولّد بحلقة
for، أو تطلب قيمة قيمة بـnext(). - المولّد الذي لا ينتهي آمن ما دمت توقف الطلب بـ
break. - المولّد يوفّر الذاكرة لأنه لا يحفظ كل القيم دفعة واحدة.
⏭️ الخطوة التالية
صرت الآن تعرف أداة لا يعرفها كثير من المبتدئين، وهذه خطوة كبيرة في تعلم لغة بايثون. جرّب أن تكتب مولّدًا يعطيك درجات الحرارة لأسبوع، أو أسئلة مسابقة واحدًا واحدًا. وفي الدرس القادم ستتعلّم كيف تحفظ بيانات برنامجك بصيغة JSON وتستعيدها، في التسلسل في بايثون. وحتى ذلك الحين، افتح بايثون اونلاين على موقع بايثون هذا وجرّب كل الأمثلة بقيم من عندك.