זכרון בפונקציות#

אג’נדה#

  • scope של פונקציות

  • משתנים מקומיים וגלוביים

  • העברת מידע לפונקציות

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

תזכורת: משתנים אקטואליים ופורמליים#

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

def what(st, c):
    if c in st:
        return True
    else:
        return False
 
print(what("hello", "t"))
print(what("hello", "e"))

st ו-c הם פרמטרים פורמליים. בעת קריאה לפונקציה, למשל what("hello", "t") מועברים לה פרמטרים אקטואליים - “hello” ו-“t”. הערך האקטואלי “hello” מוצב לתוך הפרמטר הפורמלי st, ואילו “t” מוצב לתוך c.

Scope#

כפי שחלקכם אולי שמתם לב, לא כל משתנה שנגדיר יהיה זמין בכל מקום בתוכנית. הscope של משתנה מתאר את “תחום הראייה” שלו - כלומר, באילו חלקים של התוכנית ניתן לגשת אליו או להשתמש בו.

בכל פעם שמריצים פונקציה, נוצר עבורה scope חדש ונפרד. בתוך הscope הזה חיים המשתנים המקומיים שלה - למשל, הפרמטרים שהוגדרו לפונקציה או משתנים שנוצרו בתוכה. משתנים אלו קיימים רק בזמן הריצה של הפונקציה, וברגע שהפונקציה מסתיימת - הם “נעלמים”. מחוץ לפונקציה אנחנו לא יכולים לגשת אליהם ישירות, אלא רק לראות את התוצאה שהפונקציה מחזירה.

משתנים השייכים לפונקציה מסוימת נקראים משתנים מקומיים (local). משתנים אשר לא שייכים לאף פונקציה נקראים משתנים גלובליים (global).

בואו נראה דוגמה לכך. הנה פונקציה שמגדירה משתנה בתוך הפונקציה.

def my_func():
    n=5

כעת נגדיר משתנה מחוץ לפונקציה בשם n:

n=6

מדובר בשני משתנים שונים לחלוטין עבור המחשב. יש את n הגלובלי ואת הn של הפונקציה my_func. שינוי באחד לא ישנה את השני.

n = 6
my_func()
print(n) # we will get 6 because we didn't change the global n.
6

צפו בסרטון הבא המדגים כיצד פרמטרים עוברים בין scopes

לנוחיותכם חלונית עם הקוד שראינו בסרטון בpython tutor. מוזמנים להריץ בעצמכם:

בסרטון זה ראינו איך פרמטרים שמוגדרים בפונקציה – המשתנים הפורמליים – מקבלים את הערכים שמועברים אליהם בזמן הקריאה לפונקציה, כלומר המשתנים האקטואליים. זהו שלב חשוב בהבנה של איך פונקציות “מדברות” עם שאר הקוד. לפני שנצלול לעומק לשאלה איך משתנים עוברים בין scopes שונים, נעצור רגע כדי לבחון עוד כמה דוגמאות. הדוגמאות האלו יעזרו לנו לחדד את ההבדל בין משתנים שחיים בתוך הפונקציה לבין משתנים שחיים מחוצה לה, ולראות בצורה ברורה יותר מה נגיש לנו בכל שלב של התוכנית.

הנה פונקציה שמשנה את הפרמטר הלוקאלי שלה n (מגדילה אותו ב-1):

def add1(n):
    n = n + 1
    return n

n = 6

# Add test codes here

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

בחנו את עצמכם

התבוננו בקטע הקוד הבא וענו על השאלות שאחריו:

def foo(a,b):
  print(a+b)	 	
    
a = 123
b = 456
print(a+b)
foo(b,a)

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

משתנים מקומיים וגלובליים#

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

def my_func():
    local_var = 6

print(local_var) # Error
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[16], line 4
      1 def my_func():
      2     local_var = 6
----> 4 print(local_var) # Error

NameError: name 'local_var' is not defined

לעומת זאת, משתנה גלובלי (global) מוגדר מחוץ לפונקציות, ולכן אפשר להשתמש בו מכל מקום בתוכנית – גם מתוך פונקציות.

global_var = 6
def my_func():
    print(global_var)

my_func()
6

שימו לב! אם בתוך פונקציה נגדיר משתנה בשם זהה למשתנה גלובלי, המשתנה המקומי “מסתיר” את הגלובלי, והקוד בפונקציה יתייחס אך ורק אליו.

var = 6
def my_func():
    var = 5
    print(var)

my_func()
5

העברת מידע לפונקציות וחזרה#

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

השאלה המעניינת היא – האם הפונקציה יכולה לשנות את האובייקט שהיא מקבלת כפרמטר? מדובר בדיוק במקרה שראינו בפרק הקודם - שני שמות מצביעים לאותו אובייקט.

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

def func(x):
    x.append(9)
    
a=[4,5,6,7,8]

print(a)
func(a)
print(a)
[4, 5, 6, 7, 8]
[4, 5, 6, 7, 8, 9]

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

מאחר שרשימה היא אובייקט mutable וappend מבצע שינוי של הרשימה עצמה, אז השינוי יופיע גם בa.