2.37. Named tuples และ deques

Lists, tuples, dictionaries และ sets ครอบคลุมความต้องการข้อมูลส่วนใหญ่ คอนเทนเนอร์อีกสามตัวในโมดูล collections เหมาะกับปัญหาเฉพาะที่ built-ins จัดการได้ไม่ดีนัก

2.37.1. namedtuple -- เรคคอร์ดที่มีชนิดข้อมูลโดยไม่ต้องสร้างคลาส

tuple ธรรมดาเก็บค่าตามตำแหน่ง นั่นเหมาะสำหรับ 2- หรือ 3-tuple ขนาดเล็กที่ใช้ชั่วคราว แต่เมื่อเกินนั้น point[0] และ point[1] เริ่มสื่อความหมายไม่ชัดเจน collections.namedtuple() ส่งคืน subclass 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

อาร์กิวเมนต์ fields คือลำดับของสตริงชื่อ (หรือสตริงเดียวที่คั่นด้วยช่องว่างใน CPython; MicroPython เข้มงวดกว่า -- ต้องส่ง tuple หรือ list)

ทำไมต้องใช้ namedtuple แทนคลาส?

  • มันคือ tuple การวนซ้ำ, การ unpack, ความเท่ากัน, การแฮช และการใช้เป็น dict key ล้วนใช้งานได้ฟรี

  • มันไม่เปลี่ยนแปลงได้ การกำหนดค่าใหม่ r.temp = ... จะเกิด AttributeError ซึ่งเป็นสิ่งที่ต้องการสำหรับชนิดเรคคอร์ด

  • ใช้ RAM น้อยกว่า instance ของคลาสที่มีฟิลด์เดียวกัน -- การจัดเก็บ tuple เป็นแบบต่อเนื่อง ไม่มี __dict__

เมื่อเปรียบกับคลาสที่เทียบเท่า การประกาศ namedtuple ใช้บรรทัดเดียว ข้อแลกเปลี่ยนคือฟิลด์เป็นแบบอ่านอย่างเดียว -- การ "เปลี่ยน" การอ่านค่าต้องสร้างใหม่

2.37.2. deque -- บัฟเฟอร์วงแหวนที่มีขนาดจำกัด

list เร็วที่ ปลาย (append / pop) และช้าที่ ต้น (insert(0, ...) / pop(0) ต้องเลื่อนทุกองค์ประกอบอื่น) collections.deque เร็วที่ทั้งสองด้าน -- เป็นบัฟเฟอร์วงแหวนที่จัดดัชนีด้วยตัวชี้หัวและท้าย ดังนั้น append และ pop ที่ฝั่งใดก็ทำงานในปริมาณงานคงที่เดิมโดยไม่ขึ้นกับจำนวนรายการใน deque

การสร้างใน MicroPython ต้องการทั้ง iterable เริ่มต้น และ ความยาวสูงสุด ตามลำดับนั้น:

>>> 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) -- ผนวกแต่ละรายการจาก 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 ยังให้คุณที่ dict ธรรมดาไม่มี:

  • ความเท่ากันของ OrderedDict พิจารณาลำดับ dict ธรรมดาสองตัวเปรียบเท่ากันเมื่อมีคู่ key/value เหมือนกัน โดยไม่คำนึงถึงลำดับการแทรก instance ของ 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 มีประโยชน์เมื่อคุณกำลัง serialize การตั้งค่าเป็นรูปแบบที่ ใส่ใจ ลำดับ key (TOML, ผู้บริโภค YAML บางราย) หรือเมื่อคุณต้องการคำใบ้ในเอกสารที่ชัดเจนว่าลำดับมีความสำคัญสำหรับผู้อ่านโค้ดของคุณ

สำหรับโค้ดทั่วไป ให้ใช้ dict ในตัว ใช้ OrderedDict ก็ต่อเมื่อ semantics ที่ลำดับเป็นส่วนหนึ่งของความเท่ากัน หรือคุณค่าทางเอกสารให้ประโยชน์จริงๆ

คอนเทนเนอร์ทั้งสามตัวต่างมีการใช้งานเฉพาะที่แคบ Named tuples แทนที่คลาสเรคคอร์ดที่เขียนเอง deques แทนที่ list สำหรับคิวที่มีขนาดจำกัด ordered dicts ทำให้ลำดับการแทรกเป็นส่วนหนึ่งของสัญญา