א.7 — פונקציות
בתוכנית אמיתית אותה פעולה חוזרת פעמים רבות: משחק מדפיס הודעת פתיחה בכל סיבוב, מחשב ניקוד אחרי כל מהלך ובודק שוב ושוב אם השחקן ניצח. אם נעתיק את אותן שורות קוד לכל מקום שבו הן נחוצות, התוכנית תתארך, וכל תיקון קטן יצריך שינוי בכל העותקים. הפתרון הוא לתת לקטע הקוד שם ולהפעיל אותו בשמו בכל פעם שצריך.
פונקציה היא קטע קוד בעל שם שמרכז הוראות לביצוע משימה. מגדירים אותה פעם אחת, ובכל פעם שרוצים לבצע את המשימה קוראים לה בשמה; עצם ההגדרה אינה מפעילה אותה. כבר השתמשנו בפונקציות מוכנות של פייתון, כמו print ו־len; בפרק זה נלמד לכתוב פונקציות משלנו.
בקריאה לפונקציה כותבים את שמה ואחריו סוגריים (). לפעמים הסוגריים ריקים, כמו print(), ולפעמים מעבירים בתוכם ערכים, כמו print("Hello") או len("Hello"). הסוגריים שאחרי השם מסמנים שזו קריאה להפעלת הפונקציה. כתיבת שם הפונקציה לבדו, ללא הסוגריים, אינה מפעילה אותה.
למשל, אפשר לרכז את הדפסת הודעת הפתיחה של משחק בפונקציה, ולקרוא לה בכל התחלת משחק. כך התוכנית מחולקת למשימות ברורות ונמנעת כתיבה חוזרת של אותן הוראות. נתחיל בפונקציה שמדפיסה הודעה, ובהמשך נוסיף קבלת נתונים והחזרת תוצאה.
פתיחת המחברת ב־Colab · השיעור וההרצאה באתר הקורס
מחברות האוניברסיטה הפתוחה — 4-פונקציות
מהי פונקציה וכיצד מגדירים אותה?
נתחיל בפונקציה שאינה מקבלת ערכים ואינה מחזירה תוצאת חישוב, אלא רק מדפיסה הודעה:
def greet():
print("Hello!")
print("Welcome to the game")
greet()
פלט
Hello!
Welcome to the game
defפותחת את הגדרת הפונקציה; אחריה כותבים את שמה.- הסוגריים הריקים
()מציינים שהפונקציה אינה מקבלת פרמטרים. - נקודתיים
:מסיימות את שורת ההגדרה. גוף הפונקציה מוזח בארבעה רווחים. greet()היא הקריאה לפונקציה. היא כתובה ללא הזחה, מחוץ להגדרה.
ההגדרה לבדה אינה מפעילה את גוף הפונקציה. הפקודות מתבצעות רק כשקוראים לה. לאחר סיום הפעולה ממשיכים לפקודה שאחרי הקריאה; אפשר לקרוא לאותה פונקציה שוב.
פונקציה שמקבלת ערכים
כדי שאותה פעולה תעבוד עם נתונים שונים, מוסיפים פרמטרים בסוגריים של ההגדרה. בזמן הקריאה מעבירים את הערכים, שנקראים ארגומנטים.
def show_player(name, score):
print("Player:", name)
print("Score:", score)
show_player("Dana", 10)
show_player("Noam", 7)
פלט
Player: Dana
Score: 10
Player: Noam
Score: 7
בהגדרה, name ו־score הם הפרמטרים. בקריאה הראשונה "Dana" נכנסת ל־name, ו־10 נכנס ל־score. בהעברה לפי מיקום, סדר הערכים חייב להתאים לסדר הפרמטרים. בקריאה השנייה אותה פונקציה פועלת עם ערכים אחרים.
אין חובה לציין את טיפוס הפרמטר בהגדרה. אפשר להעביר ערך ישיר, משתנה או ביטוי; לדוגמה, show_player("Dana", 5 + 5) מעבירה את הערך 10 כפרמטר השני. הפונקציה כאן רק מדפיסה את הנתונים.
ארגומנטים לפי שם
כשלפונקציה יש כמה פרמטרים, קשה לזכור את סדרם, וקריאה כמו show_player("Dana", 10) אינה אומרת לקורא מהו 10. כדי שהקריאה תהיה ברורה יותר, אפשר למסור ארגומנטים לפי שמות הפרמטרים, גם בסדר שונה.
def show_player(name, score):
print(name, score)
show_player("Dana", 10)
show_player(score=10, name="Dana")
פלט
Dana 10
Dana 10
אפשר גם לשלב את שתי הדרכים: show_player("Dana", score=10). בתחביר הזה, הארגומנטים לפי מיקום באים לפני הארגומנטים לפי שם; אין למסור פעמיים ערך לאותו פרמטר. ארגומנטים לפי שם בתיעוד Python.
ערכי ברירת מחדל
לעיתים רוב הקריאות לפונקציה משתמשות באותו ערך לפרמטר מסוים, ורק לעיתים רחוקות נדרש ערך אחר. במקום למסור את הערך הרגיל בכל קריאה, קובעים לו ערך ברירת מחדל בהגדרת הפונקציה. פרמטר עם ערך ברירת מחדל מאפשר להשמיט אותו בקריאה; כשמוסרים לו ערך, הערך שנמסר גובר על ברירת המחדל.
def show_bonus(score, bonus=5):
print(score + bonus)
show_bonus(10)
show_bonus(10, bonus=2)
פלט
15
12
בקריאה show_bonus(10) הפרמטר bonus מקבל את ברירת המחדל 5, ולכן מודפס 15. בקריאה השנייה נמסר לו במפורש הערך 2. בהגדרת פונקציה, הפרמטרים בלי ברירת מחדל באים ראשונים, ורק אחריהם הפרמטרים שיש להם ברירת מחדל.
לפעמים אין ערך רגיל שמתאים לפרמטר, ורוצים רק לדעת אם הקורא מסר לו ערך או לא. לשם כך משמש None, ערך מיוחד בפייתון שמשמעותו "אין ערך". בדוגמה הבאה הוא מציין שלא נמסר יעד. הוא שונה מהמספר 0: יעד של אפס הוא עדיין יעד שנמסר. אפשר להשתמש בו כברירת מחדל:
def show_score(score, target=None):
print("Score:", score)
if target is not None:
print("Target:", target)
show_score(10)
show_score(10, target=20)
פלט
Score: 10
Score: 10
Target: 20
בקריאה הראשונה target נשאר None, ולכן שורת היעד אינה מודפסת; בקריאה השנייה נמסר יעד, והוא מודפס. לבדיקה מול None נשתמש ב־is None או ב־is not None.
מספר משתנה של ארגומנטים
עד כה ידענו מראש כמה ערכים הפונקציה מקבלת. לפעמים המספר אינו ידוע בעת ההגדרה: למשל, פונקציה שמסכמת את הניקוד של כל השחקנים, כשמספר השחקנים משתנה ממשחק למשחק. כוכבית לפני שם הפרמטר מאפשרת לקבל מספר משתנה של ערכים. נהוג לקרוא לפרמטר כזה args, אבל אפשר לבחור שם שמתאר את הנתונים, כמו scores. בדוגמה עוברים על הערכים בלולאה ומדפיסים את סכומם:
def show_total(*scores):
total = 0
for score in scores:
total += score
print(total)
show_total(3, 7, 2)
פלט
12
הערכים נאספים ל־tuple, מבנה נתונים שנלמד בפרק א.9.
משתנים מקומיים — היכן המשתנה מוכר?
משתנה מקומי הוא משתנה שנוצר בתוך פונקציה ומשמש אותה בזמן הקריאה. למשל, פונקציה שמחשבת ציון יכולה לשמור בתוכה סכום ביניים. הקוד שמחוץ לפונקציה אינו יכול לפנות למשתנה המקומי הזה בשמו.
המושג תחום הגדרה (scope) מתאר היכן בקוד השם של המשתנה מוכר. גם הפרמטרים של פונקציה הם שמות מקומיים. ההפרדה מאפשרת לכל פונקציה להשתמש בשמות נוחים כמו score או total, בלי שהשמה אליהם תשנה משתנה בעל אותו שם בפונקציה אחרת.
אותו שם בתוך הפונקציה ומחוץ לה
משתנה שנוצר ברמה הראשית של התוכנית, מחוץ לפונקציות, נקרא משתנה גלובלי. בדוגמה הבאה יש שני משתנים בשם x: אחד גלובלי ואחד מקומי.
x = 5
def show_local():
x = 3
print("Inside:", x)
show_local()
print("Outside:", x)
פלט
Inside: 3
Outside: 5
ההשמה x = 3 בתוך הפונקציה יוצרת משתנה מקומי. לכן ההדפסה שבתוכה מציגה 3, ואילו ההדפסה שמחוץ לה מציגה את ה־x הגלובלי, שערכו נשאר 5. שם זהה אינו אומר שזה אותו משתנה.
משתנה מקומי אינו נגיש מבחוץ
בדוגמה הבאה local_score מוגדר רק בתוך הפונקציה:
def show_score_value():
local_score = 10
print("Inside:", local_score)
show_score_value()
print(local_score)
פלט
Inside: 10
NameError: name 'local_score' is not defined
ההדפסה בתוך הפונקציה מצליחה. ההדפסה שמחוץ לה נכשלת, כי השם local_score אינו מוגדר שם. מוצגות כאן ההדפסה והשורה האחרונה של הודעת השגיאה. קריאה לפונקציה אינה הופכת את המשתנים המקומיים שלה למשתנים של התוכנית הראשית. כדי להעביר תוצאה החוצה נשתמש ב־return, שיוסבר בהמשך הפרק.
בכל קריאה יש משתנים מקומיים נפרדים
בכל קריאה לפונקציה הפרמטרים מקבלים את הערכים שנמסרו באותה קריאה, וההשמות שבגוף הפונקציה מתבצעות מחדש. המשתנים המקומיים הרגילים אינם משמשים לשמירת התוצאה מקריאה קודמת:
def show_updated_score(score):
score += 5
print("Inside:", score)
original_score = 10
show_updated_score(original_score)
show_updated_score(original_score)
print("Outside:", original_score)
פלט
Inside: 15
Inside: 15
Outside: 10
בכל אחת משתי הקריאות הפרמטר המקומי score מקבל 10, ואז מתעדכן ל־15. הקריאה השנייה אינה ממשיכה מ־15 של הקריאה הראשונה. המשתנה original_score שמחוץ לפונקציה נשאר 10: ההשמה לפרמטר המספרי אינה משנה אותו.
אפשר לקרוא מתוך פונקציה גם משתנה גלובלי, אך עדיף להעביר את הנתונים הדרושים כפרמטרים ולהחזיר תוצאות במפורש. כך ברור במה הפונקציה משתמשת. בהמשך נבחין בין השמה למשתנה מקומי לבין שינוי איברים של רשימה שהועברה לפונקציה.
תיעוד פונקציה
כשתוכנית גדלה, מי שקורא אותה — חבר לצוות, או אנחנו עצמנו כעבור חודש — צריך להבין במהירות מה כל פונקציה עושה בלי לקרוא את כל הקוד שלה. לשם כך נהוג לצרף לפונקציה תיאור קצר. מחרוזת תיעוד (docstring) מתארת מה הפונקציה עושה. כותבים אותה בתחילת גוף הפונקציה, בדרך כלל בתוך שלוש מירכאות. כדאי לציין את מטרת הפונקציה, את משמעות הפרמטרים ואת התוצאה שהיא מחזירה, אם יש כזו.
def show_welcome():
"""Display the opening message of the game."""
print("Welcome to the game")
show_welcome()
פלט
Welcome to the game
מחרוזת התיעוד מסבירה לקורא את תפקיד הפונקציה; היא אינה מודפסת בזמן הקריאה. רק פקודת print מציגה כאן טקסט.
החזרת ערכים מפונקציה
פונקציה יכולה להחזיר ערך — את תוצאת החישוב שהיא ביצעה. למשל, פונקציה שמחשבת סכום של שני מספרים יכולה להחזיר את הסכום לקוד שקרא לה. כך אפשר לשמור את התוצאה במשתנה ולהשתמש בה בהמשך התוכנית, למשל לחישוב נוסף או להדפסה.
כדי להחזיר ערך בפייתון משתמשים בפקודה return. אחריה כותבים את הערך או את הביטוי שרוצים להחזיר. הפקודה מסיימת את הקריאה לפונקציה ומעבירה את התוצאה לקוד שקרא לה.
החזרת ערך יחיד: הפונקציה הבאה מחזירה את סכום שני המספרים. התוצאה נשמרת במשתנה result. הפרמטרים a ו־b הם שמות מקומיים בתוך הפונקציה; הקוד הקורא מקבל את התוצאה דרך return.
def add(a, b):
return a + b
result = add(3, 5)
print(result)
פלט
8
הדפסה אינה החזרה. print מציגה מידע; return מעבירה תוצאה לקוד הקורא. פונקציה שמגיעה לסופה בלי return, או מבצעת return ללא ערך, מחזירה None, ערך המציין כאן היעדר תוצאה.
def greet():
print("Hello")
result = greet()
print(result)
פלט
Hello
None
החזרת כמה ערכים: מפרידים ביניהם בפסיקים אחרי return. לדוגמה, פונקציה יכולה לחשב ולהחזיר את שתי הקואורדינטות של נקודה:
def next_position(x, y):
new_x = x + 1
new_y = y + 2
return new_x, new_y
x, y = next_position(3, 5)
print(x, y)
פלט
4 7
הערכים מוחזרים יחד, באותו סדר: הערך הראשון נכנס ל־x והשני ל־y. מאחורי הקלעים הם נארזים לערך אחד מסוג tuple (טאפל), שעליו נלמד בפרק א.9 — tuple. בשלב זה מספיק להכיר את האפשרות להחזיר כמה תוצאות ולקבל אותן במשתנים נפרדים.
אפשר גם לקבל את התוצאה כולה במשתנה אחד:
point = next_position(3, 5)
print(point)
פלט
(4, 7)