2.10. קבוצות#
קבוצה היא אוסף לא מסודר של פריטים ייחודיים. הוספת ערך שכבר קיים אינה משפיעה; איטרציה מניבה כל ערך פעם אחת בדיוק. קבוצות הן הכלי הנכון כאשר חברות וסילוק כפילויות חשובים, והסדר אינו חשוב.
2.10.1. יצירת קבוצה#
השתמשו בסוגריים מסולסלים עבור קבוצה לא ריקה, או ב-set() עבור קבוצה ריקה:
colours = {"red", "green", "blue"}
empty = set()
הסוגריים נראים כמו ליטרל של dict; {} לבדו הוא מילון ריק, לא קבוצה ריקה – אחת התאונות ההיסטוריות של Python. השתמשו ב-set() למקרה הריק.
set() גם בונה קבוצה מכל איטרבל, וזו הדרך הסטנדרטית לסילוק כפילויות מרצף:
nums = [1, 2, 2, 3, 1, 4]
unique = set(nums)
print(unique)
פלט:
{1, 2, 3, 4}
סדר ההדפסה עשוי להשתנות – קבוצות אינן מבטיחות לבצע איטרציה בסדר מסוים כלשהו.
2.10.2. קבוצה לעומת מילון#
קבוצות ומילונים שניהם מאחסנים פריטים ייחודיים בטבלת גיבוב. מה שכל פריט נושא עמו הוא ההבדל:
dictמאחסן זוגות מפתח-ערך. חיפוש מפתח מחזיר את ערכו.setמאחסן רק את הפריטים. חיפוש פריט אומר לכם אם הוא קיים.
הבחירה בין השניים נוגעת לשאלה האם הערך שלצד כל פריט משמעותי:
פנו ל-קבוצה כאשר אין ערך השייך לצד כל פריט – כל שאכפת לכם הוא האם הפריט קיים, או שאתם משלבים קבוצות של פריטים ייחודיים באמצעות איחוד / חיתוך.
פנו ל-מילון כאשר כל פריט מזווג עם נתון שהחיפוש אמור לאחזר – מפת הגדרות, מטמון, מונה הממופתח לפי שם.
שני הסוגים חולקים חלק ניכר מהתחביר החיצוני, וזה המקור לרוב הבלבול. ההבדלים בבלוק אחד:
set | dict | |
|---|---|---|
מחזיק | פריטים ייחודיים | מפתחות ייחודיים, כל אחד עם ערך |
ליטרל מאוכלס |
|
|
ליטרל ריק |
|
|
בדיקת חברות |
|
|
אחזור ערך | לא רלוונטי |
|
הוספת פריט |
|
|
איטרציה | מניבה פריטים | מניבה מפתחות (השתמשו ב- |
האסימטריה בין הליטרלים המאוכלסים לריקים היא המלכודת שראוי להדגיש:
סוגריים עם פריטים בתוכם –
{1, 2, 3}– הם ליטרל קבוצה; סוגריים עם זוגות מפתח-ערך –{"a": 1}– הם ליטרל מילון. המנתח מבחין ביניהם לפי מה שבפנים.סוגריים עם שום דבר בפנים –
{}– הם מילון ריק, לא קבוצה ריקה. מילונים הופיעו ראשונים; הליטרל הריק שייך להם. לקבוצה ריקה אין ליטרל סוגריים כלל, ויש לכתוב אותהset().
דפוס נפוץ כאשר רק המפתחות של מילון נקראים אי-פעם הוא לעבור לקבוצה – הדבר הופך את הכוונה לברורה ומקצץ את הערכים הלא בשימוש מהזיכרון.
2.10.3. הוספה והסרה#
set.add()– מכניס פריט אחד.set.discard()– מסיר פריט אם הוא קיים, ולא עושה דבר אם אינו קיים.set.remove()– מסיר פריט; גורם ל-KeyErrorאם הוא חסר.set.clear()– מרוקן את הקבוצה.
s = {1, 2, 3}
s.add(4)
s.discard(99) # silent: 99 not in s
s.remove(2)
print(s)
פלט:
{1, 3, 4}
2.10.4. חברות#
האופרטור in בודק חברות. בקבוצה הוא בקירוב בזמן קבוע ללא תלות בגודל – וזו הסיבה העיקרית לבחור בקבוצה על פני list כאשר כל שאתם צריכים הוא לשאול ”האם ערך זה נמצא שם“:
if "red" in colours:
print("colour is allowed")
list עם אותו תוכן תסרוק מההתחלה בכל פעם, מה שבסדר עבור עשרה פריטים אך איטי עבור עשרת אלפים.
2.10.5. פעולות על קבוצות#
ניתן לשלב שתי קבוצות באמצעות הפעולות המתמטיות הרגילות. לכל אחת יש צורת אופרטור וצורת מתודה כאחד:
a | bאוa.union(b)– כל מה שנמצא באחת הקבוצות.a & bאוa.intersection(b)– רק מה שמופיע בשתיהן.a - bאוa.difference(b)– ב-aאך לא ב-b.a ^ bאוa.symmetric_difference(b)– באחת אך לא בשתיהן.
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(a | b)
print(a & b)
print(a - b)
print(a ^ b)
פלט:
{1, 2, 3, 4, 5, 6}
{3, 4}
{1, 2}
{1, 2, 5, 6}
צורות האופרטור הן לקריאה בלבד; צורות המתודה מקבלות כל איטרבל בצד ימין, לא רק קבוצה נוספת (a.union([5, 6])). בחרו את מה שנקרא טוב יותר בהקשר.
2.10.6. מה יכול להיכנס לקבוצה#
איברי קבוצה חייבים להיות ניתנים לגיבוב (hashable) – אותה מגבלה כמו עבור מפתחות dict. int, float, str, bool, bytes, ו-tuple (כאשר תוכנו עצמו ניתן לגיבוב) כולם עובדים. list ו-dict אינם עובדים; ניסיון להוסיף אחד מהם גורם ל-TypeError.
2.10.7. frozenset#
set רגילה היא ברת-שינוי: כל קריאה ל-add / remove / discard משנה את האובייקט במקום. בריר-שינוי זו פוסלת אותה מלהיות ניתנת לגיבוב, ולכן קבוצה אינה יכולה לשמש כמפתח dict או כאיבר של קבוצה אחרת.
frozenset היא המקבילה הבלתי-משתנה. יש לה אותם חיפושים ואופרטורים (in, |, &, -, ^) כמו ל-set, אך ללא add / remove וללא מתודות המשנות אותה. מכיוון ששום דבר אינו יכול אי-פעם לשנות את תוכנה, הגיבוב של frozenset מוגדר היטב – ולכן היא כן ניתנת לגיבוב:
primary = frozenset({"red", "green", "blue"})
secondary = frozenset({"yellow", "purple", "orange"})
palettes = {
primary: "RGB",
secondary: "mixed",
}
print(palettes[primary])
פלט:
RGB
בנו frozenset מכל איטרבל – frozenset() למקרה הריק, frozenset(some_set) כדי לקחת תמונת בזק (snapshot) בלתי-משתנה של קבוצה קיימת:
snapshot = frozenset(s) # immutable copy of s
s.add("new") # snapshot does not change
שתי סיבות נפוצות לפנות אליה:
שימוש כמפתח מילון או כאיבר קבוצה. בכל מקום שערך יחיד אינו יכול לתפוס את מה שאתם צריכים,
frozensetשל ערכים יכולה – ”קבוצת המאפיינים הנתמכים על ידי דרייבר זה“, ”קבוצת הפינים שפרופיל זה משתמש בהם“.נעילת קבוע.
frozensetברמת המודול של שמות מורשים אינה יכולה להשתנות בטעות על ידי קורא;setרגילה כן. העדיפוfrozensetעבור כל דבר שאמור להיות לקריאה בלבד לאחר הבנייה.