بروتوكول التكرار (Iterator Protocol)
كل حلقة for في بايثون تعمل بنفس الآلية خلف الكواليس: تستدعي iter()
على الكائن للحصول على مُكرِّر (iterator)، ثم تستدعي next() عليه
مرارًا حتى يرمي استثناء StopIteration:
numbers = [10, 20, 30]
it = iter(numbers)
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
print(next(it)) # StopIteration ❌ (لا مزيد من العناصر)
هذا بالضبط ما تفعله for x in numbers: تلقائيًّا وتلتقط StopIteration
لإنهاء الحلقة بأناقة دون أن تراها.
المولّدات بـ yield
المولّد (generator) دالة عادية تستخدم yield بدل return. بكل استدعاء
لها لا تُنفَّذ فورًا — بل تُرجع كائن مولّد يحفظ مكانه ويُكمل من حيث
توقّف عند كل next():
def countdown(n):
while n > 0:
yield n
n -= 1
for num in countdown(3):
print(num)
# 3
# 2
# 1
💡 استدعاء
countdown(3)وحده لا يُنفّذ أي كود داخل الدالة — فقط يُنشئ كائن مولّد. الكود يعمل تدريجيًّا مع كلnext()أو تكرارfor.
لماذا نستخدم المولّدات؟ (كفاءة الذاكرة)
المولّد ينتج القيم واحدة تلو الأخرى عند الطلب، بدل بنائها كلها دفعة واحدة في الذاكرة:
| المقارنة | قائمة عادية | مولّد (Generator) |
|---|---|---|
| بناء القيم | كل القيم فورًا في الذاكرة | قيمة واحدة عند كل next() |
| الاستهلاك | يمكن المرور عليها عدة مرات | تُستهلك مرّة واحدة فقط |
| مناسبة لـ | بيانات صغيرة تحتاجها كاملة | تدفّق بيانات ضخم أو لا نهائي |
def fibonacci():
a, b = 0, 1
while True: # مولّد لا نهائي — آمن لأنه كسول
yield a
a, b = b, a + b
fib = fibonacci()
first_five = [next(fib) for _ in range(5)]
print(first_five) # [0, 1, 1, 2, 3]
بناء قائمة فيبوناتشي لا نهائية بحلقة عادية كان سيُطبق الذاكرة فورًا — المولّد يحلّها لأنه لا يبني شيئًا مسبقًا.
جرّب بنفسك
📥 المدخلات (input) — قيمة في كل سطر
أخطاء شائعة
- محاولة إعادة استهلاك مولّد استُنفد بالكامل — يبقى فارغًا، أنشئ مولّدًا جديدًا بدل إعادة استخدام القديم.
- توقّع أن استدعاء الدالة
fibonacci()ينفّذ الكود فورًا — هو فقط يُنشئ كائن المولّد. - استخدام
list(fibonacci())مباشرة على مولّد لا نهائي — يعلّق البرنامج للأبد لأنه يحاول استهلاك كل القيم.
🎯 التالي: الديكوريتورز (Decorators) — تعديل سلوك الدوال دون تغيير كودها.