dict — מילון: מפתח וערך

מטרות השיעור
  • ליצור dict ולקרוא ערך לפי מפתח
  • להוסיף, לעדכן ולמחוק זוגות מפתח-ערך
  • להשתמש ב-get עם ברירת מחדל כדי להימנע מ-KeyError
  • לעבור על מילון עם keys, values ו-items, ולעבוד עם רשימה של מילונים

מה נלמד?

dict (Dictionary, מילון) הוא מבנה שמחזיק זוגות של מפתח וערך. במקום לגשת לפי מיקום ([0]), ניגשים לפי שם (["name"]).

למה צריך את זה?

ב-tuple ("Dana", 24, "Haifa") צריך לזכור שמיקום 1 הוא הגיל. עם 15 שדות, זה מתכון לבאגים. dict פותר את זה:

student = {"name": "Dana", "age": 24, "city": "Haifa"}
student["age"]   # clear and readable

dict הוא אולי המבנה החשוב ביותר לשבועות הבאים:

  • JSON (מחר): הפורמט שבו שומרים קבצים ושולחים מידע ברשת. בפייתון הוא הופך ל-dict.
  • נושא Backend ומסדי נתונים: כל בקשה ותשובה ב-API היא בעצם dict. {"id": 7, "name": "Dana"}.
  • כל “רשומה”, כמו משתמש, מוצר או הזמנה, נוח לייצג כ-dict.

הסבר מהבסיס

מהו dict?

  • כל פריט הוא זוג: מפתח (Key) ‏←‏ ערך (Value).
  • מפתחות ייחודיים — אין שני מפתחות זהים. השמה למפתח קיים מחליפה את הערך.
  • ניתן לשינוי (Mutable).
  • שומר על סדר ההכנסה (מ-Python 3.7).
  • מפתח הוא בדרך כלל str או int. ערך יכול להיות כל דבר: מספר, טקסט, רשימה, ואפילו dict אחר.
אנלוגיה: מילון / ספר טלפונים

במילון אמיתי מחפשים מילה (מפתח) ומקבלים הגדרה (ערך). לא מחפשים “את ההגדרה במקום ה-5,000 בספר”. בספר טלפונים: שם ‏←‏ מספר. אי אפשר ששני אנשים יופיעו עם בדיוק אותו שם ככניסות נפרדות. כניסה חדשה עם אותו שם מחליפה את הקודמת.

תחביר

# create
empty = {}
student = {"name": "Dana", "age": 24}

# read
student["name"]                 # "Dana" - KeyError if the key does not exist
student.get("email")            # None - no error if missing
student.get("email", "N/A")     # "N/A" - default value if missing

# add / update (same syntax!)
student["email"] = "dana@mail.com"   # new key -> added
student["age"] = 25                  # existing key -> updated

# delete
del student["email"]            # delete a key (KeyError if missing)
age = student.pop("age")        # delete and return the value

# check
"name" in student               # True - checks KEYS, not values
len(student)                    # number of key-value pairs

# iterate
for key in student:             # keys
for value in student.values():  # values
for key, value in student.items():   # pairs (tuples!)

דוגמת קוד ראשונה

week-2/student_dict.py:

student = {"name": "Dana", "age": 24, "city": "Haifa"}

print("Name:", student["name"])
print("Age:", student["age"])

student["age"] = 25
student["email"] = "dana@mail.com"
print("Updated:", student)

del student["city"]
print("After delete:", student)

print("Has email?", "email" in student)
print("Phone:", student.get("phone", "not provided"))

פלט:

Name: Dana
Age: 24
Updated: {'name': 'Dana', 'age': 25, 'city': 'Haifa', 'email': 'dana@mail.com'}
After delete: {'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
Has email? True
Phone: not provided

פירוק הקוד שורה אחר שורה

שורההסבר
{"name": "Dana", ...}מפתח, נקודתיים, ערך. זוגות מופרדים בפסיקים.
student["name"]קריאה לפי מפתח: הערך "Dana".
student["age"] = 25המפתח קיים, ולכן הערך מתעדכן.
student["email"] = "..."המפתח לא קיים, ולכן הוא נוסף (בסוף). אותו תחביר בדיוק!
del student["city"]מחיקת הזוג כולו.
"email" in studentבודק אם המפתח קיים.
student.get("phone", "not provided")המפתח לא קיים, ולכן מוחזרת ברירת המחדל, בלי שגיאה.

דוגמה נוספת

רשימה של מילונים — המבנה הנפוץ ביותר לנתונים “אמיתיים” (טבלה: כל dict הוא שורה). וגם ספירה עם dict. week-2/products.py:

products = [
    {"id": 1, "name": "Laptop", "price": 3500, "category": "electronics"},
    {"id": 2, "name": "Mouse", "price": 80, "category": "electronics"},
    {"id": 3, "name": "Desk", "price": 900, "category": "furniture"},
    {"id": 4, "name": "Chair", "price": 450, "category": "furniture"},
]

print("--- Catalog ---")
for product in products:
    print(product["id"], product["name"], product["price"], "NIS")

expensive = []
for product in products:
    if product["price"] > 500:
        expensive.append(product["name"])
print("Over 500 NIS:", expensive)

count_by_category = {}
for product in products:
    category = product["category"]
    count_by_category[category] = count_by_category.get(category, 0) + 1
print("Count by category:", count_by_category)

for category, count in count_by_category.items():
    print(category, "->", count, "products")

פלט:

--- Catalog ---
1 Laptop 3500 NIS
2 Mouse 80 NIS
3 Desk 900 NIS
4 Chair 450 NIS
Over 500 NIS: ['Laptop', 'Desk']
Count by category: {'electronics': 2, 'furniture': 2}
electronics -> 2 products
furniture -> 2 products

פירוק:

  • products היא list שכל איבר בה הוא dict. product["price"] = המחיר של המוצר הנוכחי בלולאה.
  • תבנית הספירה: count_by_category.get(category, 0) + 1. אם הקטגוריה עדיין לא במילון, get מחזיר 0, ואז 0+1 = 1. אם היא כבר שם, מקבלים את הספירה הקיימת ומוסיפים 1. זו תבנית שחוזרת שוב ושוב.
  • .items() — מחזיר זוגות (key, value) כ-tuples, ו-for category, count in מפרק אותם (Unpacking מהשיעור הקודם).

dict בתוך dict

ערך יכול להיות dict בעצמו:

user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl 10"},
    "hobbies": ["chess", "running"],
}
print(user["address"]["city"])   # Haifa
print(user["hobbies"][0])        # chess

ניגשים “שכבה אחר שכבה”: user["address"] מחזיר dict, ועליו ["city"]. זה בדיוק המבנה של JSON.

טעות נפוצה

הטעות 1: KeyError על מפתח שלא קיים.

student = {"name": "Dana"}
print(student["age"])
KeyError: 'age'

הפתרון: student.get("age") (מחזיר None), student.get("age", 0) (ברירת מחדל), או לבדוק קודם עם if "age" in student:.

הטעות 2: לחפש ערך עם in.

student = {"name": "Dana"}
print("Dana" in student)
False

in על dict בודק מפתחות. לבדיקת ערכים: "Dana" in student.values().

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

student = {"name": "Dana", "age": 24}
student["Age"] = 25
print(student)
{'name': 'Dana', 'age': 24, 'Age': 25}

אין שגיאה! פשוט נוסף מפתח חדש "Age", ו-"age" נשאר 24. מפתחות רגישים לאותיות גדולות וקטנות. זה Logical Error שקט, ולכן שומרים על שמות מפתחות עקביים (snake_case).

הטעות 4: רשימה כמפתח.

locations = {[32, 35]: "Haifa"}
TypeError: unhashable type: 'list'

מפתח חייב להיות Immutable. אם צריך מפתח של כמה ערכים, משתמשים ב-tuple: {(32, 35): "Haifa"}. (נסביר את הקשר ל-Immutable בשיעור הבא.)

תרגול

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

תרגיל 1: כרטיס סטודנט

צרו מילון student עם השם "Dana", הגיל 24 והעיר "Haifa". אחר כך:

  1. עדכנו את הגיל ל-25.
  2. הוסיפו אימייל "dana@mail.com".
  3. מחקו את העיר.
  4. הדפיסו את המילון, ואת הטלפון בעזרת get עם ברירת המחדל "none".

פלט צפוי:

{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none
הצג פתרון
student = {"name": "Dana", "age": 24, "city": "Haifa"}

student["age"] = 25
student["email"] = "dana@mail.com"
del student["city"]

print(student)
print(student.get("phone", "none"))
{'name': 'Dana', 'age': 25, 'email': 'dana@mail.com'}
none

student["phone"] היה זורק KeyError. get מחזיר ערך ברירת מחדל כשהמפתח לא קיים.

תרגיל 2: ספירת מילים

כתבו פונקציה count_words(text) שמחזירה מילון: כל מילה (באותיות קטנות) ‏←‏ כמה פעמים היא מופיעה.

counts = count_words("the cat and the dog and THE bird")
print(counts)

פלט צפוי:

{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}
הצג פתרון
def count_words(text):
    counts = {}
    for word in text.lower().split():
        counts[word] = counts.get(word, 0) + 1
    return counts


counts = count_words("the cat and the dog and THE bird")
print(counts)
{'the': 3, 'cat': 1, 'and': 2, 'dog': 1, 'bird': 1}

counts.get(word, 0) מחזיר 0 למילה שעוד לא נספרה — כך לא צריך if נפרד למילה חדשה.

תרגיל 3: רשימה של מילונים

products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]
  1. הדפיסו את שמות המוצרים שמחירם מתחת ל-1000.
  2. בנו מילון שסופר כמה מוצרים יש בכל קטגוריה, והדפיסו אותו.

פלט צפוי:

Mouse
Desk
{'electronics': 2, 'furniture': 1}
הצג פתרון
products = [
    {"name": "Laptop", "price": 3500, "category": "electronics"},
    {"name": "Mouse", "price": 80, "category": "electronics"},
    {"name": "Desk", "price": 900, "category": "furniture"},
]

for product in products:
    if product["price"] < 1000:
        print(product["name"])

counts = {}
for product in products:
    category = product["category"]
    counts[category] = counts.get(category, 0) + 1
print(counts)
Mouse
Desk
{'electronics': 2, 'furniture': 1}

תרגיל 4: מעבר על מילון

stock = {"apple": 12, "banana": 0, "cherry": 5}

עברו על המילון עם items() והדפיסו כל פריט ואת הכמות. לפריט שהכמות שלו 0 הדפיסו out of stock.

פלט צפוי:

apple -> 12
banana -> out of stock
cherry -> 5
הצג פתרון
stock = {"apple": 12, "banana": 0, "cherry": 5}

for name, quantity in stock.items():
    if quantity == 0:
        print(name, "->", "out of stock")
    else:
        print(name, "->", quantity)
apple -> 12
banana -> out of stock
cherry -> 5

items() מחזיר זוגות של מפתח וערך, ו-Unpacking בלולאה מפרק כל זוג לשני משתנים.

תרגיל 5: מילון מקונן

user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}

הדפיסו את העיר, את התחביב הראשון, והוסיפו תחביב "music". בסוף הדפיסו את רשימת התחביבים.

פלט צפוי:

Haifa
chess
['chess', 'running', 'music']
הצג פתרון
user = {
    "name": "Dana",
    "address": {"city": "Haifa", "street": "Herzl"},
    "hobbies": ["chess", "running"],
}

print(user["address"]["city"])
print(user["hobbies"][0])
user["hobbies"].append("music")
print(user["hobbies"])
Haifa
chess
['chess', 'running', 'music']

כל [ ] נכנס רמה אחת פנימה: user["address"] הוא מילון, ו-["city"] שולף ממנו. זה בדיוק המבנה של JSON.

סיכום

  • dict {key: value} — גישה לפי מפתח ולא לפי מיקום. מפתחות ייחודיים.
  • d[key] = value — מוסיף אם חדש, מעדכן אם קיים.
  • d[key] זורק KeyError על מפתח חסר. d.get(key, default) לא זורק.
  • in בודק מפתחות. מעבר: keys(), values(), items() (זוגות).
  • רשימה של מילונים = טבלה. dict בתוך dict = JSON.
  • תבנית ספירה: counts[x] = counts.get(x, 0) + 1.

דוקומנטציה רשמית