Python/namedtuple

من موسوعة حسوب

تعطي الصفوف المسمّاة معنى لكل عنصر ضمن الصف، وينتج عن ذلك شيفرة أسهل في القراءة وموثّقة ذاتيًّا. يمكن استخدام الصفوف المسمّاة في أيّ مكان تستخدم فيه الصفوف العادية، وتمتاز بإمكانية الوصول إلى الحقول عن طريق الأسماء بدلًا من فهرس العنصر.

البنية العامة

تستخدم الدالة namedtuple()‎ لإنشاء الصفوف المسمّاة وصيغتها العامّة هي:

namedtuple(typename, field_names, *, verbose=False, rename=False, module=None)

تعيد هذه الدالة البانية صنفًا فرعيًا من الصنف tuple يحمل الاسم typename. يُستخدم الصنف الفرعي الجديد في إنشاء كائنات شبيهة بالصفوف تمتلك حقولًا يمكن الوصول إليها عن طريق البحث في الخاصيات إضافة إلى إمكانية فهرسة هذه الحقول والمرور عليها. تمتلك نسخ الكائن الفرعي كذلك سلسلة توثيق نصية (docstring) مفيدة (مع typename و field_names) وتابع ‎__repr__()‎ وظيفته سرد قائمة بمحتويات الصف على هيئة name=value.

field_names هي تسلسل من السلاسل النصية مثل ['x', 'y']، أو يمكن أن تكون سلسلة نصية مفردة تكون فيها أسماء الحقول مفصولة عن بعضها البعض بمسافة بيضاء و/أو فاصلة، مثل: 'x y' أو 'x, y'.

يمكن استخدام أي معرّف صالح في بايثون كاسمٍ للحقل باستثناء الأسماء التي تبدأ بالشرطة السفلية. تشمل المعرّفات الصالحة الحروف، والأرقام والشرطات السفلية، ولا تبدأ بالأرقام أو الشرطات السفلية ولا يمكن أن تكون إحدى الكلمات المفتاحية مثل class أو for أو return أو global أو pass أو raise.

إن أخذ الوسيط rename القيمة True فإنّ أسماء الحقول غير الصالحة تبدّل بصورة تلقائية إلى أسماء موقعية. فمثلًا تحوّل ['abc', 'def', 'ghi', 'abc'] إلى ‎['abc', '_1', 'ghi', '_3']‎ إذ تحذف الكلمة المفتاحية def واسم الحقل المكرر abc.

إن أخذ الوسيط verbose القيمة True فإنّ تعريف الصنف يطبع بعد بنائه. هذا الوسيط مهمل الآن، ومن الأسهل طباعة الخاصية ‎_source بدلًا من ذلك.

إن تم تعريف الوسيط module فإنّ الخاصية ‎__module__‎ التابعة للصف المسمى تأخذ هذه القيمة.

لا تمتلك الصفوف المسمّاة قاموسًا لكل نسخة منها؛ ولهذا تستهلك الصفوف المسمّاة نفس مقدار الذاكرة الذي تستهلكه الصفوف العادية.

ملاحظات:

  • أضيف المعامل rename في الإصدار 3.1 من اللغة.
  • أصبح المعاملان verbose و rename معاملين مفتاحيين فقط في الإصدار 3.6 من اللغة.
  • أضيف الوسيط module في الإصدار 3.6 من اللغة.

توابع الصنف namedtuple

تقدّم الصفوف المسمّاة -إضافة إلى التوابع الموروثة من الصفوف العادية- ثلاثة توابع إضافية. ولتجنب حدوث أي تعارض مع أسماء الحقول، تبدأ أسماء التوابع بشرطة سفلية.

التابع ‎_make()‎

تابع صنف يُنشئ نسخة جديدة من الصنف namedtuple باستخدام تسلسل أو كائن مكرّر.

التابع ‎_asdict()‎

يعيد التابع كائن OrederedDict جديدًا يربط بين أسماء الحقول والقيم المرتبطة بها.

التابع‎_replace()‎

يعيد التابع نسخة جديدة من الصفّ المسمّى تُستبدل فيها الحقول المعطاة بالقيم الجديدة.

خصائص الصنف namedtuple

تقدّم الصفوف المسمّاة -إضافة إلى الخصائص الموروثة من الصفوف العادية- خاصيتين إضافيتين تبدأ أسمائهما بشرطة سفلية لتجنب حدوث أي تعارض مع أسماء الحقول.

الخاصية ‎_fields

تعيد الخاصية صفًّا من السلاسلة النصية التي تتضمّن أسماء الحقول. يمكن الاستفادة من هذه الخاصية في التعرّف على أسماء الحقول أو في إنشاء صفّ مسمّى جديد من صفّ مسمّى موجود.

>>> p._fields            # عرض أسماء الحقول
('x', 'y')

>>> Color = namedtuple('Color', 'red green blue')
>>> Pixel = namedtuple('Pixel', Point._fields + Color._fields)
>>> Pixel(11, 22, 128, 255, 0)

الخاصية ‎_fields_defaults

هذه الخاصية هي قاموس يربط أسماء الحقول بالقيم الافتراضية.

>>> Account = namedtuple('Account', ['type', 'balance'], defaults=[0])
>>> Account._fields_defaults
{'balance': 0}
>>> Account('premium')
Account(type='premium', balance=0)

أمثلة وتطبيقات

>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(11, y=22)     # إنشاء نسخة باستخدام وسائط موقعية أو مفتاحية
>>> p[0] + p[1]             # يمكن فهرستها مثل الصفوف العادية
33
>>> x, y = p                # فك التحزيم مثل الصفوف العادية
>>> x, y
(11, 22)
>>> p.x + p.y               # يمكن الوصول إلى الحقول عن طريق اسمائها
33
>>> p                       # تمثيل سهل القراءة
Point(x=11, y=22)

يمكن الحصول على الحقل الذي يكون اسمه مخزّنًا في سلسلة نصية باستخدام الدالة getattr()‎:

>>> getattr(p, 'x')
11

يُستخدم عامل النجمة المزدوجة لتحويل قاموس إلى صفّ مسمّى (كما هو موضّح في قسم فك تحزيم قوائم المعاملات):

>>> d = {'x': 11, 'y': 22}
>>> Point(**d)
Point(x=11, y=22)

لمّا كانت الصفوف المسمّاة أصناف عادية في بايثون، فإنّ من السهل إضافة المزيد من الوظائف أو تعديل الموجود منها عن طريق تفريع الصنف. يبين المثال التالي كيفية إضافة حقل محسوب وتنسيق طباعة ذي عرض ثابت إلى الصفّ المسمّى:

>>> class Point(namedtuple('Point', ['x', 'y'])):
...     __slots__ = ()
...     @property
...     def hypot(self):
...         return (self.x ** 2 + self.y ** 2) ** 0.5
...     def __str__(self):
...         return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

>>> for p in Point(3, 4), Point(14, 5/7):
...     print(p)
Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

في الشيفرة السابقة جرى إسناد صفّ فارغ إلى المتغيّر __slots__، ويساعد هذا الإجراء على تخفيض متطلبات الذاكرة إذ يمنع من إنشاء قواميس خاصّة بنسخة الكائن. ولكن لا يجدي تفريع الأصناف نفعًا عند الحاجة إلى إضافة حقل جديد مفروز. بل الأفضل إنشاء صف مسمّى جديد من الخاصّية ‎_fields:

>>> Point3D = namedtuple('Point3D', Point._fields + ('z',))

يمكن تخصيص سلاسل التوثيق النصية عن طريق الإسناد المباشر إلى حقول __doc__:

>>> Book = namedtuple('Book', ['id', 'title', 'authors'])
>>> Book.__doc__ += ': Hardcover book in active collection'
>>> Book.id.__doc__ = '13-digit ISBN'
>>> Book.title.__doc__ = 'Title of first printing'
>>> Book.authors.__doc__ = 'List of authors sorted by last name'

ملاحظة: في الإصدار 3.5 من اللغة أصبحت الخاصية docstrings قابلة للكتابة. يمكن استخدام قيمة افتراضية باستخدام التابع ‎_replace()‎ وذلك لتخصيص النسخة الأولية prototype instance:

>>> Account = namedtuple('Account', 'owner balance transaction_count')
>>> default_account = Account('<owner name>', 0.0, 0)
>>> johns_account = default_account._replace(owner='John')
>>> janes_account = default_account._replace(owner='Jane')

الصفوف المسمّاة مفيدة للغاية عند تعيين أسماء الحقول إلى صفوف معادة من وحدة sqlite3 أو csv (القيم المفصولة بفاصلة):

EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
import csv
for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
    print(emp.name, emp.title)
import sqlite3
conn = sqlite3.connect('/companydata')
cursor = conn.cursor()
cursor.execute('SELECT name, age, title, department, paygrade FROM employees')
for emp in map(EmployeeRecord._make, cursor.fetchall()):
    print(emp.name, emp.title)

انظر أيضًا

مصادر

قسم namedtuples في صفحة Containers Datatypes في توثيق بايثون الرسمي.