Skip to content
> 💻 🧠 קוד 1001 > 📑 דפי עזר > > העתקת אובייקטים בפייתון

העתקת אובייקטים בפייתון

🔎 למה העתקת אובייקטים כל כך חשובה?

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

list_a = [1, 2, 3]
list_b = list_a

אתם לא יוצרים רשימה חדשה. אתם פשוט יוצרים הפניה נוספת (list_b) שמצביעה על אותו אובייקט רשימה בדיוק.

👈 כל שינוי ב-list_a ישתקף מיד ב-list_b (ולהיפך), מכיוון ששניהם מצביעים על אותו אובייקט.

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

⚙️ שני סוגי העתקה

המודול copy מספק שתי פונקציות מרכזיות:

  • copy.copy() – יוצר העתקה שטחית (shallow copy).
  • copy.deepcopy() – יוצר העתקה עמוקה (deep copy).

ההבדל המרכזי ביניהן הוא האופן שבו הן מטפלות באובייקטים מקוננים (כמו רשימות בתוך רשימות או מילונים בתוך מילונים).


🟦 העתקה שטחית (Shallow Copy)

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

import copy

original_list = [1, 2, [3, 4]]
shallow_copy = copy.copy(original_list)

# נשנה את הרשימה המקוננת במקור
original_list[2][0] = 99

print(f"מקור: {original_list}")        # פלט: מקור: [1, 2, [99, 4]]
print(f"העתקה שטחית: {shallow_copy}") # פלט: העתקה שטחית: [1, 2, [99, 4]]

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

דיאגרמת ASCII:

original_list → [ 1, 2, ───► list_inner ]
shallow_copy  → [ 1, 2, ───► list_inner ]
                            list_inner   → [ 99, 4 ]

🟩 העתקה עמוקה (Deep Copy)

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

import copy

original_list = [1, 2, [3, 4]]
deep_copy = copy.deepcopy(original_list)

# נשנה את הרשימה המקוננת במקור
original_list[2][0] = 99

print(f"מקור: {original_list}")       # פלט: מקור: [1, 2, [99, 4]]
print(f"העתקה עמוקה: {deep_copy}")   # פלט: העתקה עמוקה: [1, 2, [3, 4]]

כעת, deep_copy הוא אובייקט עצמאי לחלוטין.

דיאגרמת ASCII:

original_list → [ 1, 2, ───► list_a ]
deep_copy     → [ 1, 2, ───► list_b ]
                            list_a   → [ 99, 4 ]
                            list_b   → [ 3, 4 ]

📑 טבלת השוואה: copy.copy() מול copy.deepcopy()

סוג האובייקטדוגמהcopy.copy() (שטחית)copy.deepcopy() (עמוקה)
list (רשימה)[1, 2, [3, 4]]רשימה חדשה, אך הרשימה הפנימית [3, 4] משותפתעותק עצמאי לחלוטין; גם הרשימה הפנימית מועתקת
dict (מילון){"a": 1, "b": {"c": 2}}מילון חדש, אך המילון הפנימי {"c": 2} משותףמילון עצמאי וכל המילונים המקוננים מועתקים
set (קבוצה){1, 2, (3, 4)}קבוצה חדשה; איברים פנימיים שהם mutable יהיו משותפיםקבוצה חדשה; כל האיברים הפנימיים מועתקים רקורסיבית
tuple(1, 2, [3, 4])מחזיר את אותו ה-tuple, אך הרשימה הפנימית משותפתtuple חדש עם עותק של הרשימה הפנימית
str, int, float, bool"hi", 42, 3.14, Trueמחזיר את אותו האובייקט (הם immutable, אין טעם להעתיק)זהה להעתקה שטחית
מחלקה מותאמת אישיתNode(1, Node(2))אובייקט חדש ברמה העליונה; אובייקטים פנימיים משותפיםעותק עצמאי לחלוטין של האובייקט וכל האובייקטים הפנימיים
אובייקט עם __slots__מחלקה עם __slots__רק האובייקט ברמה העליונה מועתקכל האובייקטים הפנימיים מועתקים אם הם תומכים בהעתקה
קובץ / סוקט / streamopen("file.txt")שגיאה (אובייקט שאינו ניתן להעתקה)שגיאה (אובייקט שאינו ניתן להעתקה)

🧪 דוגמאות חיות לטבלה

📌 list

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # [1, 2, [99, 4]]
print(f"deepcopy: {c}")  # [1, 2, [3, 4]]

📌 dict

import copy
a = {"a": 1, "b": {"c": 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a["b"]["c"] = 42
print(f"copy: {b}")      # {'a': 1, 'b': {'c': 42}}
print(f"deepcopy: {c}")  # {'a': 1, 'b': {'c': 2}}

📌 set

import copy
a = {1, 2, (3, 4)} # קבוצות יכולות להכיל רק טיפוסים שהם immutable
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (אך אלו שני אובייקטים נפרדים בזיכרון)

📌 tuple

import copy
a = (1, 2, [3, 4]) # ה-tuple הוא immutable, אך איבר הרשימה שבתוכו לא
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}")      # (1, 2, [99, 4])
print(f"deepcopy: {c}")  # (1, 2, [3, 4])

📌 מחלקה מותאמת אישית

import copy
class Node:
    def __init__(self, value, child=None):
        self.value = value
        self.child = child

a = Node(1, Node(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.child.value = 99
print(f"copy: {b.child.value}")      # 99
print(f"deepcopy: {c.child.value}")  # 2

📌 התאמה אישית של התנהגות: __copy__ ו-__deepcopy__

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

import copy
class Custom:
    def __init__(self, x):
        self.x = x
    def __copy__(self):
        print("נקראה המתודה __copy__!")
        # החזרת מופע חדש, אך עם אותה הפניה למידע הפנימי
        return Custom(self.x)
    def __deepcopy__(self, memo):
        # memo הוא מילון למעקב אחר אובייקטים שכבר הועתקו (למניעת רקורסיה)
        print("נקראה המתודה __deepcopy__!")
        # יצירת עותק חדש לחלוטין, כולל המידע המקונן
        return Custom(copy.deepcopy(self.x, memo))

obj = Custom([1, 2])
c1 = copy.copy(obj)       # פלט: נקראה המתודה __copy__!
c2 = copy.deepcopy(obj)   # פלט: נקראה המתודה __deepcopy__!

⚡ ביצועים ומלכודות

  • מהירות: copy.copy() מהירה משמעותית מכיוון שהיא לא עוברת רקורסיבית על כל מבנה הנתונים. copy.deepcopy() יכולה להיות איטית מאוד עבור אובייקטים גדולים ומקוננים לעומק.
  • אובייקטים שאינם ניתנים להעתקה: משאבי מערכת כמו קבצים פתוחים, סוקטים או זרמי נתונים (streams) אינם ניתנים להעתקה. ניסיון לעשות זאת יגרום לשגיאה.
  • הפניות מעגליות: deepcopy חכמה מספיק כדי להתמודד עם הפניות מעגליות (כאשר אובייקט A מפנה ל-B, ו-B מפנה בחזרה ל-A) מבלי להיכנס ללולאה אינסופית.

מתי להשתמש בכל שיטה: כללי האצבע

  1. השתמשו בהעתקה שטחית (copy.copy()) אם האובייקט שלכם מכיל רק מידע immutable, או אם אתם מעוניינים במכוון שהעותק והמקור יחלקו אובייקטים פנימיים. זה מהיר ויעיל.
  2. השתמשו בהעתקה עמוקה (copy.deepcopy()) כאשר אתם זקוקים לעותק עצמאי לחלוטין, ב-100%, מהמקור, במיוחד בעבודה עם מבני נתונים מורכבים (רשימות, מילונים, מופעים של מחלקות).
  3. עבור אובייקטים מסוג immutable (מספרים, מחרוזות, tuples ללא איברים משתנים), לרוב אין טעם בהעתקה – פייתון ממילא מבצעת אופטימיזציה לשימוש בהם.
  4. זכרו שניתן להתאים אישית את התהליך באמצעות __copy__ ו-__deepcopy__ לשליטה מלאה.

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *