א.5 — תנאים ובקרת זרימה

עד עכשיו כתבנו תוכניות שבהן ההוראות מתבצעות לפי סדר השורות, מתחילת התוכנית ועד סופה. אבל לפעמים רוצים שקוד מסוים יתבצע רק במצב מסוים, ושבמצב אחר יתבצע קוד אחר.

בקרת זרימה היא השליטה בסדר ביצוע ההוראות ובבחירה אילו מהן יתבצעו. אחת הדרכים לעשות זאת היא באמצעות תנאים: בודקים ביטוי בוליאני, ולפי התוצאה שלו — True או False — קובעים איזה קטע קוד לבצע ועל איזה קטע לדלג.

למשל, במשחק אפשר להציג הודעת ניצחון אם הניקוד הגיע ליעד, ואחרת להציג הודעה שהמשחק ממשיך. כך אותה תוכנית מבצעת קוד שונה בהתאם לנתונים. את הכלי לניסוח השאלה כבר יש לנו: בפרק א.3 ראינו שהשוואה כמו score >= 10 מחזירה ערך בוליאני. בפרק זה נלמד לכתוב את התנאי ואת ההוראות שיתבצעו בכל מצב, ואיך לשלב כמה בדיקות לתנאי אחד.

מחברות האוניברסיטה הפתוחה — 2-בקרת זרימה

מבנה התנאי והזחה

משפט תנאי מורכב משני חלקים: השאלה שבודקים, וקבוצת ההוראות שיתבצעו אם התשובה חיובית. קבוצת הוראות כזאת נקראת בלוק (Block), ופייתון צריכה לדעת בדיוק אילו שורות שייכות לו ואילו לא.

if בודקת תנאי: אם התוצאה היא True, מתבצעות ההוראות ששייכות לתנאי; אם התוצאה היא False, מדלגים עליהן. אחרי התנאי כותבים נקודתיים (:).

בשפות אחרות מסמנים את גבולות הבלוק בסוגריים מסולסלים; בפייתון עושים זאת אחרת. בלוק מוגדר באמצעות הזחה — רווחים בתחילת השורה. בדוגמאות נשתמש בארבעה רווחים לכל רמת הזחה. כל הפקודות באותו בלוק מיושרות לאותה רמה; חזרה לרמת ההזחה הקודמת מסיימת את הבלוק.

כדי לשמור על הזחה אחידה, השתמשו במקש Tab במקום לספור רווחים ידנית. כל השורות באותו בלוק צריכות להתחיל באותה רמת הזחה. אם שורה אחת מוזחת בארבעה רווחים ושורה אחרת באותו בלוק בשלושה, עלולה להופיע שגיאת הזחה (IndentationError).

תנאי יחיד — if

הצורה הפשוטה ביותר היא if לבדו: בודקים תנאי, ואם הוא מתקיים מבצעים בלוק; אם לא — פשוט ממשיכים הלאה. בדוגמה הבאה נציג הודעת ניצחון רק כשהניקוד הגיע ל־10, ובכל מקרה נדפיס שהבדיקה הסתיימה:

score = 12

if score >= 10:
    print("Win")

print("Check finished")

פלט

Win
Check finished

הקוד שומר את הניקוד 12 ובודק אם הוא גדול או שווה ל־10. התנאי מתקיים, ולכן מודפסת הודעת הניצחון. אחר כך מודפסת Check finished: השורה הזאת אינה מוזחת, ולכן היא מתבצעת בלי קשר לתוצאת הבדיקה. אילו הניקוד היה 7, היה מתבצע דילוג על הדפסת Win, ורק הודעת הסיום הייתה מודפסת. אפשר להשתמש ב־if בלבד, בלי שום ענף נוסף.

בחירה בין שתי אפשרויות — if–else

אם רוצים לבצע פעולה אחרת כשהתנאי אינו מתקיים, מוסיפים else. אחרי else כותבים נקודתיים, ללא תנאי נוסף:

score = 7

if score >= 10:
    print("Win")
else:
    print("Keep playing")

פלט

Keep playing

הניקוד הוא 7, ולכן התנאי score >= 10 אינו מתקיים. הקוד מדלג על ענף ה־if ומבצע את ענף ה־else, שמדפיס הודעה להמשיך לשחק. אם הניקוד יהיה 10 ומעלה, תודפס רק Win. במבנה if–else מתבצע אחד משני הענפים.

בחירה בין כמה אפשרויות — elif

כשיש יותר משתי אפשרויות, מוסיפים elif עם תנאי נוסף. אפשר לכתוב כמה ענפי elif, בזה אחר זה. פייתון בודקת את התנאים מלמעלה למטה ומבצעת רק את הענף הראשון שהתנאי שלו מתקיים:

score = 7

if score >= 10:
    print("Win")
elif score >= 5:
    print("Almost there")
elif score >= 0:
    print("Keep playing")
else:
    print("Invalid score")

פלט

Almost there

הבדיקה הראשונה נכשלת כי 7 קטן מ־10. הבדיקה השנייה מצליחה כי 7 גדול או שווה ל־5, ולכן מודפסת Almost there. לאחר מכן מדלגים על יתר השרשרת — גם על הבדיקה score >= 0, שהייתה מתקיימת בפני עצמה. ניקוד בין 0 ל־4 יוביל ל־Keep playing, וניקוד שלילי יגיע ל־else ויוביל ל־Invalid score.

לא חייבים את כל המבנה: אפשר if בלבד, if–else, או if עם ענף elif אחד או יותר, עם או בלי else בסוף. אם אין else ואף תנאי לא מתקיים, מדלגים על כל הענפים וממשיכים אחרי השרשרת.

שילוב תנאים — and, or, not

ביטויים בוליאניים משלבים בעזרת המילים and, or ו־not. and דורשת ששתי הבדיקות יתקיימו; or דורשת שלפחות אחת תתקיים; not הופכת את הערך הבוליאני.

lives = 3
game_over = False

if lives > 0 and not game_over:
    print("Your turn")

פלט

Your turn

לשחקן נשארו שלושה ניסיונות, ולכן lives > 0 מתקיים. המשתנה game_over הוא False, ולכן not game_over הוא True. שתי הבדיקות מתקיימות, והקוד מדפיס Your turn. אם לא נשארו ניסיונות או שהמשחק הסתיים, ההדפסה לא תתבצע.

לפחות תנאי אחד מתקיים — or

משתמשים ב־or כשמספיקה אחת מכמה אפשרויות. התוצאה היא True אם לפחות אחד מהביטויים הבוליאניים הוא Trueגם אם שניהם אמת. רק כששניהם False, התוצאה היא False.

print("True or False:", True or False)
print("False or True:", False or True)
print("True or True:", True or True)
print("False or False:", False or False)

פלט

True or False: True
False or True: True
True or True: True
False or False: False

בשלוש השורות הראשונות לפחות ערך אחד הוא אמת. בשורה האחרונה שני הערכים שקר, ולכן התוצאה שקר.

לדוגמה, ניקוד תקין צריך להיות בין 0 ל־100. די שהוא קטן מ־0 או גדול מ־100 כדי לפסול אותו:

score = 120

if score < 0 or score > 100:
    print("Invalid score")
else:
    print("Valid score")

פלט

Invalid score

120 אינו קטן מ־0, אבל הוא גדול מ־100. הבדיקה השנייה מספיקה כדי שהתנאי המשולב יתקיים. אם נשנה את הניקוד ל־50, שתי הבדיקות יהיו שקר ותודפס Valid score.

בדיקה שהערך נמצא בין שני גבולות

בפייתון אפשר לכתוב בדיקת טווח בצורה ישירה: 0 <= score <= 100. הביטוי בודק שהניקוד גדול או שווה ל־0 וגם קטן או שווה ל־100. הוא שקול ל־score >= 0 and score <= 100.

score = 50

if 0 <= score <= 100:
    print("Valid score")
else:
    print("Invalid score")

פלט

Valid score

50 נמצא בטווח, ולכן מודפסת הודעה שהניקוד תקין. הסימנים <= כוללים גם את 0 ואת 100. אם רוצים ערך שנמצא ממש בין הגבולות, בלי לכלול אותם, כותבים 0 < score < 100.

סדר הפעולות בתנאי משולב וסוגריים

כמו בפעולות החשבון, גם בתנאים יש קדימות: השוואות קודמות ל־not, אחריו and, ולבסוף or. סוגריים משנים את הקיבוץ. למשל, not score > 10 פירושו not (score > 10). קדימות האופרטורים בתיעוד Python.

נניח שאפשר להיכנס למקום עם כרטיס או הזמנה, אבל רק כשהמקום פתוח:

has_ticket = True
is_invited = False
is_open = False

plain = has_ticket or is_invited and is_open
grouped = (has_ticket or is_invited) and is_open
print("Without parentheses:", plain)
print("With parentheses:", grouped)

פלט

Without parentheses: True
With parentheses: False

ללא סוגריים, and קושרת את בדיקת הפתיחה רק להזמנה: הביטוי שקול ל־has_ticket or (is_invited and is_open). הכרטיס לבדו מספיק לקבלת אמת, אפילו כשהמקום סגור. עם סוגריים, בדיקת הפתיחה חלה על שתי האפשרויות, ולכן מתקבל שקר — כפי שהתכוונו. כשמשלבים and ו־or, כדאי להוסיף סוגריים שמבהירים את התנאי הרצוי.

שרשרת תנאים לעומת בדיקות נפרדות

בשרשרת ifelifelse נבחר ענף אחד. כשכותבים שני משפטי if נפרדים, כל אחד נבדק בפני עצמו:

num = 0
if num < 10:
    print("Less than 10")
if num == 0:
    print("Zero")

פלט

Less than 10
Zero

הערך של num הוא 0: הוא גם קטן מ־10 וגם שווה ל־0. מכיוון שאלו שתי פקודות if נפרדות, שתיהן נבדקות ושתי ההודעות מודפסות.

תנאי בתוך תנאי

לפעמים בדיקה אחת הגיונית רק לאחר שבדיקה אחרת הצליחה. ההזחה קובעת לאיזה תנאי שייך כל ענף:

word = "Apple"
if word.isalpha():
    if word[0] == "A":
        print("Starts with A")
    else:
        print("Another first letter")
else:
    print("Not a word")

פלט

Starts with A

תחילה נבדק אם word מכילה רק אותיות. עבור "Apple" התשובה חיובית, ולכן נכנסים לבלוק ובודקים אם התו הראשון הוא "A". גם בדיקה זו מצליחה, ומודפסת Starts with A. ה־else הפנימי מיועד למילה שמתחילה באות אחרת; ה־else החיצוני מיועד לטקסט שאינו מורכב מאותיות בלבד.

בחירת ערך בביטוי אחד

לעיתים כל מה שהתנאי צריך להחליט הוא איזה ערך לשמור במשתנה — למשל איזו הודעה להציג. במקום ארבע שורות של ifelse עם שתי השמות, אפשר לכתוב זאת בשורה אחת. אחרי שמבינים תנאי רגיל, אפשר לכתוב בחירה קצרה: הערך אם התנאי מתקיים, if, התנאי, else והערך האחר.

x = -5
message = "positive" if x > 0 else "non positive"
print(message)

פלט

non positive

הערך של x הוא ‎−5, ולכן x > 0 אינו מתקיים. הביטוי בוחר במחרוזת "non positive", שומר אותה במשתנה message, ואז מדפיס אותה.