الفصل 11

حلقات for ودالة range — التكرار لعدد محدد من المرات

عندما تعرف عدد مرات التكرار، فإن حلقة for هي الخيار المثالي. الأشكال الثلاثة لدالة range، ولماذا يُستثنى الحد الأخير، وكيف تختار بين for و while.

35 دقيقةPython 3.12
  1. 1المشكلة
  2. 2الفهم
  3. 3أمثلة محلولة
  4. 4التوقع
  5. 5التطبيق
  6. 6التحدي

المشكلة التي نقوم بحلها

في الدرس السابق، تطلّب حساب مجموع الأرقام من 1 إلى 5 أربعة أسطر، سطران منها كانا مخصصين فقط لتتبع العدّ — قيمة ابتدائية ومقدار زيادة. وإذا نسيت مقدار الزيادة، فستقع في حلقة لانهائية.

لكن لاحظ أمراً مهماً حول هذا النوع من المهام: كنا نعرف مسبقاً عدد المرات التي سيتكرر فيها التنفيذ. خمس مرات. وعندما تكون هذه هي الحالة، لا يوجد سبب لتحمل عناء إدارة العداد بنفسك — يمكنك ترك هذه المهمة لبايثون بالكامل.

هذا هو بالضبط ما تفعله حلقة for. ولأن العدّ لم يعد بيدك، فإن حلقة for لا يمكن أن تصبح لانهائية أبداً — وهذه هي أكبر ميزة لها على الإطلاق.

في نهاية هذا الدرس ستكون قادراً على

  • تشغيل الكود لعدد معروف من المرات باستخدام حلقة for
  • استخدام الأشكال الثلاثة لدالة range()
  • شرح لماذا ينتج عن range(1, 5) أربعة أرقام وليس خمسة
  • التكرار على أحرف نص معين حرفاً بحرف
  • تحديد ما إذا كانت المهمة تتطلب استخدام for أم while

المتطلبات السابقة: حلقات while — التكرار طالما تحقق الشرط.


أبسط صورة لحلقة for

python
for number in range(5):
    print(number)
text
0
1
2
3
4

ثلاثة أسطر تم اختصارها في سطر واحد، ودون أي عداد تديره بنفسك. اقرأ هذا السطر على النحو التالي: "لكل رقم من الأرقام التي تنتجها دالة `range(5)`، نفّذ الكتلة البرمجية مرة واحدة — وفي كل مرة، ضع ذلك الرقم في المتغير `number`."

يظهر هنا أمران بوضوح:

يبدأ العدّ من الصفر، والعدد 5 لا يظهر أبداً — هناك خمسة أرقام إجمالاً، لكنها تبدأ من 0 حتى 4. هذه هي نفس القاعدة التي تطبقها بايثون في كل مكان: تماماً كما يُستثنى فهرس النهاية في شرائح النصوص، يُستثنى أيضاً في range.

اسم المتغير number هو اختيارك أنت. يمكنك كتابة for i in range(5) بنفس الطريقة، ويُعد i الاسم المعتاد للعدادات البرمجية. ولكن عندما يكون للرقم معنى محدد، اختر اسماً يعبر عن هذا المعنى — فكتابة for student_number in range(30) تشرح نفسها بنفسها.

الأشكال الثلاثة لدالة range()

python
print("--- range(5) ---")
for i in range(5):
    print(i, end=" ")

print()
print("--- range(2, 6) ---")
for i in range(2, 6):
    print(i, end=" ")

print()
print("--- range(0, 10, 3) ---")
for i in range(0, 10, 3):
    print(i, end=" ")

print()
print("--- range(5, 0, -1) ---")
for i in range(5, 0, -1):
    print(i, end=" ")
print()
text
--- range(5) ---
0 1 2 3 4
--- range(2, 6) ---
2 3 4 5
--- range(0, 10, 3) ---
0 3 6 9
--- range(5, 0, -1) ---
5 4 3 2 1

القواعد:

  • range(stop) — يبدأ من 0، وينتهي قبل stop
  • range(start, stop) — يبدأ من start، وينتهي قبل stop
  • range(start, stop, step) — يتقدم بمقدار الخطوة step في كل مرة؛ والخطوة السالبة تعني العد تنازلياً إلى الخلف

تأمل المثالين الأخيرين بعناية. الكود range(0, 10, 3) يعطي 0 3 6 9 — الرقم التالي سيكون 12 وهو أكبر من 10، لذلك يتوقف. والكود range(5, 0, -1) يعطي 5 4 3 2 1 — الرقم 0 لا يظهر أبداً لأن النهاية مستثناة هنا أيضاً.

لماذا يُستثنى الحد الأخير؟ لأن ذلك يجعل الحسابات الرياضية في غاية البساطة. كم عدداً يوجد في range(0, 5)؟ الناتج هو 5 - 0، أي خمسة أعداد. وفي range(3, 8)؟ الناتج هو 8 - 3، أي خمسة أعداد. ما عليك سوى الطرح لتحصل على العدد الإجمالي، دون حيرة في التساؤل عما إذا كان يجب عليك إضافة واحد أم لا. وإذا وضعت نطاقين متجاورين — مثل range(0, 5) و range(5, 10) — فلن تفوت أي رقم ولن تكرر أي رقم.

عندما تريد الأرقام من واحد إلى عشرة

يتكرر هذا الخطأ كثيراً لدرجة أنه يستحق قسماً خاصاً به:

python
print("Wrong — stops at 9:")
for i in range(1, 10):
    print(i, end=" ")

print()
print("Right — includes 10:")
for i in range(1, 11):
    print(i, end=" ")
print()
text
Wrong — stops at 9:
1 2 3 4 5 6 7 8 9
Right — includes 10:
1 2 3 4 5 6 7 8 9 10

القاعدة في سطر واحد: اكتب كقيمة توقف (stop) رقماً أكبر بواحد من آخر رقم تريده.

تجميع القيم — الآن بالطريقة الأسهل

python
total = 0

for number in range(1, 6):
    total = total + number

print("Sum of 1 to 5:", total)
text
Sum of 1 to 5: 15

قارن هذا بنسخة while لنفس المهمة: لا يوجد عداد تنشئه، ولا عداد تزيده يدوياً، ولا خطر الوقوع في حلقة لانهائية. الشيء الوحيد الذي يجب الانتباه له هو أن المجمّع (accumulator) لا يزال بحاجة إلى أن يبدأ خارج الحلقة، تماماً كما في السابق.

التكرار على النصوص

لا تتنقل حلقة for عبر الأرقام فحسب — بل يمكنها المرور على النصوص حرفاً بحرف:

python
word = "Python"

for letter in word:
    print(letter, end="-")
print()

vowel_count = 0
for letter in "programming":
    if letter in "aeiou":
        vowel_count = vowel_count + 1

print("Vowels:", vowel_count)
text
P-y-t-h-o-n-
Vowels: 3

لا توجد دالة range هنا لعدم الحاجة إليها — فالعبارة for letter in word تمر مباشرة على كل حرف. هذا أبسط بكثير من إجراء حسابات الفهارس يدوياً، ولا يعرّضك لخطر الوقوع في خطأ IndexError.

وعندما تحتاج إلى موضع الحرف (الفهرس) والحرف نفسه معاً، يمكنك استخدام دالة enumerate()، ولكن سنتناول ذلك بالتفصيل في درس القوائم.

break و continue يعملان هنا أيضاً

يعمل الأمران تماماً كما كانا يعملان في حلقة while:

python
for number in range(1, 20):
    if number % 7 == 0:
        print("First multiple of 7:", number)
        break
text
First multiple of 7: 7

الاختيار بين for و while

اطرح على نفسك سؤالاً واحداً: هل عدد التكرارات معروف قبل أن تبدأ الحلقة؟

إذا كانت الإجابة نعم، فاستخدم for:

  • التكرار خمس مرات
  • الانتقال من 1 إلى 100
  • فحص كل حرف في كلمة
  • القيام بعمل ما مع كل عنصر في قائمة

إذا كانت الإجابة لا، فاستخدم while:

  • الاستمرار في السؤال حتى تصبح المدخلات صحيحة
  • التكرار حتى يكتب المستخدم كلمة quit
  • القراءة طالما بقيت أسطر في الملف

عند الشك، اختر for. والسبب بسيط: حلقة for مضمونة الانتهاء والتوقف، بينما حلقة while ليست كذلك. وحيثما يعمل كلاهما، فإن الأداة التي تقلل احتمالية الأخطاء هي دائماً الخيار الأفضل.


مثال متكامل

multiplication.py:

python
# Print a multiplication table, then a small summary
number = int(input("Which table? "))
upto = int(input("Up to?       "))

print()
print(f"--- Table of {number} ---")

total = 0
for i in range(1, upto + 1):
    result = number * i
    total = total + result
    print(f"{number} x {i:2} = {result:4}")

print()
print(f"Rows    : {upto}")
print(f"Sum     : {total}")
print(f"Average : {total / upto:.2f}")
text
Which table? 7
Up to?       5

--- Table of 7 ---
7 x  1 =    7
7 x  2 =   14
7 x  3 =   21
7 x  4 =   28
7 x  5 =   35

Rows    : 5
Sum     : 105
Average : 21.00

ثلاثة أشياء تستحق الانتباه. أولاً، range(1, upto + 1) — الوصول إلى upto استلزم كتابة رقم أكبر بواحد. ثانياً، {i:2} و {result:4} — داخل نصوص f-string يمكنك تحديد عرض المسافة التي يجب أن يشغلها الرقم، مما يحافظ على استقامة الأعمدة ومحاذاتها بدقة. ثالثاً، يبدأ total خارج الحلقة وينمو بداخلها — وهو نفس نمط المجمّع الذي رأيناه سابقاً.

واسأل نفسك سؤالاً واحداً: ماذا يحدث لو أجاب المستخدم بـ 0 عند السؤال عن Up to? سينتج عن range(1, 1) نطاق فارغ تماماً بدون أي أرقام، وبالتالي لن تنفذ الحلقة إطلاقاً — وعندها ستتسبب العملية total / upto في خطأ القسمة على صفر. هذا هو نفس الدرس المستفاد من الدرس السابق: قبل استخدام ما قامت الحلقة بتجميعه، تأكد من أنها قامت بتجميع أي شيء بالفعل.


حالات الخطأ الشائعة

الرقم الأخير مفقود ينتهي range(1, 10) عند الرقم 9. أضف واحداً إلى حد التوقف: range(1, 11).

TypeError: 'float' object cannot be interpreted as an integer لا تقبل دالة range() إلا أعداداً صحيحة. كتابة range(2.5) أو range(1, n / 2) لن تعمل — حوّل القيمة باستخدام int()، أو استخدم القسمة الصحيحة //.

SyntaxError: expected ':' نسيان النقطتين الرأسيتين في نهاية سطر for.

IndentationError: expected an indented block after 'for' statement السطر داخل الحلقة لم يُزح بمسافة بادئة.

الحلقة لم تنفّذ إطلاقاً نطاق range فارغ. ينتج عن range(5, 0) نطاق لا يحتوي على شيء — للعد تنازلياً تحتاج إلى الوسيط الثالث: range(5, 0, -1).

المجموع يساوي 0 بعد انتهاء الحلقة سطر total = 0 وُضع بالخطأ داخل الحلقة، مما يؤدي لإعادة ضبطه في كل دورة. يجب أن يكون خارج الحلقة.

متغير من داخل الحلقة مطلوب خارجها، ولكنه يسبب NameError كان نطاق range فارغاً، فلم تنفّذ الحلقة أبداً ولم يُنشأ المتغير أصلاً. امنحه قيمة ابتدائية قبل الحلقة.