mutable vs. immutable בזכרון#

אג’נדה#

  • הפניות לערכים - השמות מאחורי הקלעים.

  • ההבדל בין mutables וimmutables.

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

הפניות לערכים mutable לעומת immutable מנקודת מבט של הזכרון#

בפייתון, כל מידע מאוחסן באובייקטים. כשאנחנו כותבים למשל a=3, נוצר אובייקט חדש שמייצג את המספר שלוש, והמשתנה a הוא שם שמצביע עליו.
אם אחר כך נגדיר b=a, לא ייווצר אובייקט חדש, אלא שם חדש אשר מצביע בדיוק לאותו אובייקט.
כלומר, מה שמועתק הוא ההפנייה (החץ), ולא הערך עצמו.

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

השמה של רשימות#

נראה כעת דוגמא שתעזור לנו להבין מה קורה מאחורי הקלעים:

orig_list = [1,2,3]
copy_list = orig_list 
orig_list = [6,7,8,9]

אחרי שתי השורות הראשונות, השמות orig_list וcopy_list מצביעים לאותו אובייקט - שהוא רשימה [1,2,3].

בשורה השלישית מתבצעת השמה מחדש של orig_list. כלומר כעת השם orig_list מצביע לאובייקט אחר לגמרי - [6,7,8,9].

האם שינינו או פגענו איכשהוא בקישור בין copy_list לבין האובייקט [1,2,3]?

print(copy_list)
print(orig_list)
[1, 2, 3]
[6, 7, 8, 9]

התשובה היא לא. החץ המצביע בין copy_list לבין האובייקט נשאר זהה, והרשימה לא השתנתה.

אבל מה יקרה אם נבצע עריכה ממשית של האובייקט המשותף?

orig_list = [1,2,3]
copy_list = orig_list 
orig_list[0] = 1000

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

ולכן הערך של האיבר הראשון השתנה לcopy_list “מתחת לאף”.

print(orig_list)
print(copy_list)
[1000, 2, 3]
[1000, 2, 3]

משתנים ניתנים לשינוי (mutable) לעומת שאינם ניתנים לשינוי (immutable)#

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

my_list = [1, 2, 4, 5, 7, 8]
my_list[4] = 500

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

a=5
b=a
a+=1

print(a, b)
6 5

המשתנה a מפנה כעת לאובייקט אחר, בדיוק כמו אם השורה השלישית הייתה אומרת a=6.

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

דוגמא עם משתנים הניתנים לשינוי#

עקבו כעת באמצעות Python Tutor אחר הקוד שלפניכם:

לאחר הרצת שתי שורות הקוד הראשונות, ניתן לראות כי גם L1 וגם L2 מצביעים לתחילת הרשימה. זה גם מה שקורה בפועל. בשלב הבא, כשנשנה את L1[0] ל5, אז השינוי יתבצע במצביע של מיקום 1 ברשימה. כלומר השינוי יבוא לידי ביטוי גם בL2.

ומה יקרה אם עכשיו נוסיף את השורה L2 = L2 + [7]? הוסיפו את השורה לpython tutor ובדקו.