2.37. Named tuples ו-deques

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

2.37.1. namedtuple – רשומות מוקלדות ללא מחלקה

tuple פשוט מאחסן ערכים לפי מיקום. זה בסדר עבור tuple קטן וקצר-מועד בן 2 או 3 איברים, אך מעבר לכך point[0] ו-point[1] מתחילים לשקר לגבי מה שהם מחזיקים. collections.namedtuple() מחזיר תת-מחלקה חדשה של tuple ששדותיה בעלי שמות:

>>> from collections import namedtuple
>>> Reading = namedtuple('Reading', ('temp', 'humidity', 'ts'))
>>> r = Reading(22.5, 41.0, 137204)
>>> r.temp
22.5
>>> r.humidity
41.0
>>> r[0]
22.5

ארגומנט השדות הוא רצף של מחרוזות שם (או מחרוזת אחת מופרדת ברווחים ב-CPython; MicroPython מחמיר יותר – העבר tuple או רשימה).

למה להשתמש ב-namedtuple במקום במחלקה?

  • זהו tuple. איטרציה, פירוק (unpacking), שוויון, גיבוב (hashing), ושימוש כמפתח מילון – כולם עובדים בחינם.

  • הוא בלתי ניתן לשינוי (immutable). הקצאה מחדש של r.temp = ... מעלה AttributeError, וזה בדיוק מה שאתה רוצה עבור סוג רשומה.

  • הוא עולה פחות RAM ממופע מחלקה עם אותם שדות – האחסון של ה-tuple רציף, ללא __dict__.

בהשוואה למחלקה המקבילה, הצהרת namedtuple היא שורה אחת. התמורה היא ששדות הם לקריאה בלבד – כדי ”לשנות“ קריאה אתה יוצר חדשה.

2.37.2. deque – חוצץ טבעת (ring buffer) חסום

רשימה מהירה בקצה (append / pop) ואיטית בהתחלה (insert(0, ...) / pop(0) שניהם מסיטים כל איבר אחר). collections.deque מהירה בשני הקצוות – זהו חוצץ טבעת (ring buffer) המאונדקס לפי מצביעי ראש וזנב, כך ש-append ו-pop בכל צד רצים באותה כמות עבודה קבועה ללא קשר לכמה פריטים ה-deque מחזיקה.

בנייה ב-MicroPython דורשת גם איטרבל התחלתי וגם אורך מרבי, בסדר זה:

>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
...     events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]

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

השיטות החשופות ב-deque של MicroPython הן מינימליות במכוון:

  • append(x) – הוספה לימין.

  • appendleft(x) – הוספה לשמאל.

  • extend(iterable) – הוספת כל פריט מהאיטרבל.

  • pop() – הסרה והחזרה של הקצה הימני. מעלה IndexError כשריק.

  • popleft() – הסרה והחזרה של הקצה השמאלי.

השמטות בולטות מה-deque של CPython: ללא clear, count, index, remove, reverse, rotate, מאפיין maxlen, או __contains__. איטרציה ואינדוקס באמצעות subscript עובדים:

>>> events[0]
3
>>> for e in events:
...     print(e)

שימוש טיפוסי: שמירת מספר קריאות החיישן האחרונות כדי לזהות שינויי מגמה:

history = deque((), 10)

def push(reading):
    history.append(reading)
    if len(history) == 10 and history[-1] > 2 * history[0]:
        print('reading is climbing')

2.37.3. OrderedDict – כאשר הסדר הוא חלק מהשוויון

dict רגיל שומר על סדר ההכנסה מאז MicroPython 1.13 ו-CPython 3.7. זה מכסה את הסיבה הנפוצה ביותר שבגללה אנשים נהגו לפנות אל collections.OrderedDict.

מה ש-OrderedDict עדיין נותן לך שמילון רגיל אינו נותן:

  • השוויון של OrderedDict מתחשב בסדר. שני מילונים רגילים משווים שווים בכל פעם שיש להם אותם זוגות מפתח/ערך, ללא קשר לסדר ההכנסה. שני מופעי OrderedDict שווים רק אם הזוגות שלהם באותו סדר:

    >>> from collections import OrderedDict
    >>> OrderedDict([('a', 1), ('b', 2)]) == OrderedDict([('b', 2), ('a', 1)])
    False
    >>> {'a': 1, 'b': 2} == {'b': 2, 'a': 1}
    True
    
  • OrderedDict שימושי כאשר אתה מסדר תצורה לפורמט שכן אכפת לו מסדר המפתחות (TOML, חלק מצרכני YAML), או כאשר אתה רוצה רמז תיעודי ברור שהסדר חשוב לקוראי הקוד שלך.

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

לכל אחד משלושת המכלים יש התאמה צרה אחת. Named tuples מחליפים מחלקות רשומה שנכתבו ידנית; deques מחליפים רשימות עבור תורים חסומים; ordered dicts הופכים את סדר ההכנסה לחלק מהחוזה.