2.37. الصفوف المسماة وطوابير الطرفين (deques)¶
تغطي القوائم والصفوف (tuples) والقواميس والمجموعات معظم احتياجات البيانات. هناك ثلاث حاويات أخرى في وحدة collections تناسب مشكلات محددة تتعامل معها الأنواع المدمجة بشكل أخرق.
2.37.1. namedtuple -- سجلات منمّطة دون صنف¶
يخزّن الصف العادي (tuple) القيم حسب الموضع. هذا جيد لصف صغير قصير العمر من قيمتين أو ثلاث، ولكن بعد ذلك يبدأ point[0] و point[1] في الكذب بشأن ما يحملانه. تعيد collections.namedtuple() صنفاً فرعياً جديداً من الصفوف تحمل حقوله أسماءً:
>>> 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 فأكثر صرامة -- مرّر صفاً أو قائمة).
لماذا تستخدم namedtuple بدلاً من صنف؟
إنه صف (tuple). التكرار، وفك التغليف، والمساواة، والتجزئة، والاستخدام كمفتاح قاموس، كل ذلك يعمل بلا تكلفة.
إنه غير قابل للتغيير. تؤدي إعادة إسناد
r.temp = ...إلى إطلاقAttributeError، وهو بالضبط ما تريده لنوع سجل.يكلّف ذاكرة RAM أقل من نسخة صنف لها نفس الحقول -- فتخزين الصف متجاور، دون
__dict__.
مقارنة بالصنف المكافئ، فإن تصريح namedtuple سطر واحد. المقايضة هي أن الحقول للقراءة فقط -- ولكي "تغيّر" قراءة ما، تنشئ واحدة جديدة.
2.37.2. deque -- مخزن مؤقت حلقي محدود¶
القائمة سريعة عند النهاية (append / pop) وبطيئة عند البداية (insert(0, ...) / pop(0) كلاهما يزيح كل عنصر آخر). أما collections.deque فسريعة عند كلا الطرفين -- إنها مخزن مؤقت حلقي مفهرس بمؤشري الرأس والذيل، لذا تستغرق الإضافة والإخراج على أي جانب نفس المقدار الثابت من العمل بغض النظر عن عدد العناصر التي يحملها الطابور.
يتطلب الإنشاء في MicroPython كلاً من قابل للتكرار ابتدائي و طول أقصى، بهذا الترتيب:
>>> from collections import deque
>>> events = deque((), 5)
>>> for i in range(8):
... events.append(i)
>>> list(events)
[3, 4, 5, 6, 7]
عندما يمتلئ طابور طرفين محدود، يُسقط كل append أقدم عنصر. هذا يجعل الطابور مثالياً لـ"آخر N عينة"، أو "آخر N سطر سجل"، أو أي نافذة متدحرجة لا تحتاج إلى النمو إلى الأبد.
الدوال المتاحة في طابور الطرفين الخاص بـ MicroPython محدودة عمداً:
append(x)-- الإضافة إلى اليمين.appendleft(x)-- الإضافة إلى اليسار.extend(iterable)-- إلحاق كل عنصر من القابل للتكرار.pop()-- إزالة الطرف الأيمن وإعادته. يطلقIndexErrorعند الفراغ.popleft()-- إزالة الطرف الأيسر وإعادته.
إغفالات بارزة عن طابور الطرفين في CPython: لا توجد clear أو count أو index أو remove أو reverse أو rotate أو خاصية maxlen أو __contains__. التكرار والفهرسة بالدليل السفلي يعملان:
>>> 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 إلا عندما تكون دلالات الترتيب-جزء-من-المساواة أو القيمة التوثيقية تشتري شيئاً فعلياً.
كل من الحاويات الثلاث لها استخدام ضيق واحد. تحل الصفوف المسماة محل أصناف السجلات المصنوعة يدوياً؛ وتحل طوابير الطرفين محل القوائم للطوابير المحدودة؛ وتجعل القواميس المرتبة ترتيب الإدراج جزءاً من العقد.