class Vector:
def __init__(self, *args):
self.elements = args
def __add__(self, other):
result = []
if isinstance(other, Vector):
for i in range(len(self.elements)):
result.append(self.elements[i] + other.elements[i])
else:
for i in range(len(self.elements)):
result.append(self.elements[i] + other)
return Vector(*result)
def __radd__(self, other):
return self + other
def __repr__(self):
return f"Vector({self.elements})"
13 التجريد
ومن الخصائص الممتعة في بناء البرمجيات: العلاقات بين المجردات في المستويات المختلفة من التجريد. وسنمثل لذلك بـ:
- التعميم: بحيث يكون النوع غير مقيَّد بحالة معيَّنة، بل يُخصص عند التعيين.
- التركيب: حيث نعرِّف صِنف المضلَّع أنه مركَّب من سلسلة + نقاط ونركِّب من عوامل اللغة ما يجعله يشبه المجموعات
- التخصيص: حيث نخفي أفعال السلسلة (الحذف والإضافة والتعديل ونحوها) وراء أفعال محددَّة في نوع جديد نسميه الطابور
- التسلسل: وهو النوع الذي يرتبط بمثله. كالشجرة، فروعها أصولٌ لفروع، وهكذا يتسلسل حتى نصل إلى الثمار.
13.1 التعميم
نريد أن نعمم نوع المتجه بحيث لا يتقيد بعنصرين x,y
بل يقبل أن يكون أكثر من ذلك:
- لاحظ استعمال النجمة في فعل الإنشاء
*args
وذلك لتكون عدد العناصر غير مقيدة بحد - وفي
__add__
لنا حالتان: ما إذا كان المدخل عبارة عن متجهًا أو عددًا - أما السطر الأخير منها:
return Vector(*result)
فعلامة النجمة تفك القائمة بحيث تمرر عناصرها كمعيَّنات منفصلة لفعل الإنشاء الذي تم تعريفه في الأعلى.
13.2 التركيب
لو كان عندنا تعريف نوع نقطة ..
يمكننا تركيب شيء مكون من النقاط، وهو المضلَّع، على النحو التالي:
- يأخذ فعل الإنشاء
__init__
سلسلة من النقاط ويعيِّنُها لمتغير اسمهpoints
- يعرض فعل التمثيل
__repr__
كلمةPolygon
وداخل أقواسها يضع تمثيل النقاط - لاحظ أن فعل
perimeter
يُنتج قُطر المضلَّع؛ وهو مجموع المسافات بين النقاط المتوالية
لاحظ أن إنشاء المضلَّع يتطلَّب إنشاء نقاط. ولاحظ أننا جعلنا كل نقطة في سطر، لكنها في الحقيقة كلها عناصر لقائمة، وهو واضحٌ بملاحظة القوسين المربعين [ ]
:
Polygon([Point(0, 0), Point(5, 0), Point(5, 5), Point(0, 5)])
والآن يمكن استعمال فعل حساب المحيط:
ولأن هذا المركَّب مكوَّن من أفراد، فنريده أن يُشابه الأنواع الحاوية، التي لها أفعال معيَّنة. وذلك ممكنٌ باستعمال الأفعال المخصصة:
- العد:
len(s)
يُسمَّى فعله:__len__
- الإشارة:
s[i]
يُسمَّى فعله:__getitem__
- التعيين:
s[i] = p
يُسمَّى فعله:__setitem__
- الحذف:
del s[i]
يُسمَّى فعله:__delitem__
انظر التوثيق الرسمي لبايثون حول محاكاة أنواع الحاويات لمزيد من المعلومات.
class Polygon:
def __init__(self, points):
self.points = points
def __repr__(self):
return f"Polygon({self.points})"
def __len__(self):
return len(self.points)
def __getitem__(self, i):
return self.points[i]
def __setitem__(self, i, p):
self.points[i] = p
def __delitem__(self, i):
del self.points[i]
def perimeter(self):
n = len(self.points)
s = 0
for i in range(n):
s += Point.distance(self.points[i], self.points[(i+1)%n])
return s
Polygon([Point(0, 0), Point(5, 0), Point(5, 5), Point(0, 5)])
والآن نستطيع عدَّ النقاط بالفعل المعروف len
الذي بدوْره يستدعي الفعل المتعلق بالشيء __len__
(فُهما سواء):
بتعريفنا لفعل معرفة الطول، انسجم هذا النوع مع المجموعات مثل القائمة ومجموعة الفرائد والروابط والسلسلة النصية وغيرها.
وقد تتساءل: هل أي كلمة أكتبها بين الشرطتين السفليتين استطيع استعماله مثل len
؟ والجواب: للأسف لا؛ وإنما هي أسماء مخصوصة.
أما قبول الفعل len
لأنواع مغايرة عن بعضها فتسمَّى خاصيَّة تعدد الأشكال (Polymorphism): أي أن الفعل الواحد يقبل أنواع متعددة في الحقيقة لكنَّها تُظهر نفس السلوك الخارجي. بعبارة أخرى نقول:
- الظاهر: المدخلات والمخرجات واحدة
- الحقيقة: الخوارزمية (التفاصيل) قد تختلف اختلافًا جذريًّا !
وهذا كثيرٌ في البرمجيات.
ثم الإشارة برقم أو بشريحة:
Point(0, 0)
Point(0, 5)
[Point(5, 0), Point(5, 5)]
[Point(0, 5), Point(5, 5), Point(5, 0), Point(0, 0)]
ثم التعيين:
وأخيرًا الحذف:
تبين بهذا محاكاة بعض أفعال الحاويات الأساسية مثل list
وset
وtuple
وstr
وdict
. وقد تقدَّم معنا أن النقطة تستطيع محاكاة الأنواع العددية باستعمال الفعل المخصص للجمع +
الذي هو __add__
.
13.3 التخصيص
يكثر استعمال الطابور كهيكل بيانات لحل مجموعة من المسائل بطريقة سهلة. نستعرض في هذا المثال كيفية تخصيص القائمة (list
) ذات الأفعال الكثيرة من الحذف والإضافة والتعديل، لنوع له أفعال مختلفة لكنها مبنية عليها.
الطابور يُعرَّف بفعلين أساسيين:
- الإدخال من جهة
- الإخراج من الجهة المقابلة (بحيث يكون ما دخل أولاً هو الذي يخرج أولاً)
فهو بهذا أخص من القائمة التي تزيد عليه: الإشارة لعنصر برقمٍ وإمكان تعديله أو حذفه أو الإضافة في الوسط، وغيرها.
فهذا مثال لكتابة نوع الطابور:
class Queue:
def __init__(self):
self.items = []
def enqueue(self, item):
"""Add an item to the end of the queue"""
self.items.append(item)
def dequeue(self):
"""Remove an item from the front of the queue"""
if not self.is_empty():
return self.items.pop(0)
return None
def peek(self):
"""Return the item at the front without removing it"""
if not self.is_empty():
return self.items[0]
return None
def is_empty(self):
"""Check if the queue is empty"""
return len(self.items) == 0
def __len__(self):
return len(self.items)
def __repr__(self):
"""Return the underlying list representation"""
return repr(self.items)
- لاحظ أن فعل الإنشاء
__init__
يعيِّن قائمة فارغة للشيءself
- ثم الفعل
enqueue
يتم بواسطة فعل القائمةappend
- والفعل
dequeue
يتم بواسطة فعل القائمةpop
- وكذلك
peek
باستعمال الإشارة بالرقم للعنصر في الموضع0
- ثم
is_empty
بالنظر لطول القائمة بالفعلlen
نختبر ذلك الآن:
نزيل عُنصرًا:
هل يمكننا معرفة عدد العناصر؟:
صحيح أن القائمة أكثر قدرة (إذْ تتضمن عمليات أكثر)، إلا أن الطابور مخصص لخدمة وظيفة محددة.
13.4 التسلسل
ومن الأنواع ما يتسليل مثل الشجرة، حيث فروعها قد تكون أصولاً لفروع أخرى. وهكذا يتسلسل الأمر حتى نصل إلى الأوراق / الثمار (القيم الأخيرة).
ومثل هذا له استعمالات كثيرة في هيكلة البيانات بأمثل بنية تكون فيها أسرع في البحث والإدراج والحذف ونحو ذلك. لكننا نكتفي بمثال نتأمله، نعرف فيه الشجرة الثنائية:
class BinaryTreeNode:
def __init__(self,
value,
left: 'BinaryTreeNode' = None,
right: 'BinaryTreeNode' = None):
self.value = value
self.left = left
self.right = right
def __repr__(self):
if self.left is None and self.right is None:
return f"{self.value}"
return f"({self.left}) <-- [{self.value}] --> ({self.right})"
(5) <-- [10] --> (15)
tree.left.left = BinaryTreeNode(3)
tree.left.right = BinaryTreeNode(7)
tree.right.left = BinaryTreeNode(13)
tree.right.right = BinaryTreeNode(17)
tree
((3) <-- [5] --> (7)) <-- [10] --> ((13) <-- [15] --> (17))
نكتفي بهذا القدر.