بايثونIterators
- مفهوم الـ
Iterator
- الفرق بين الـ
Iterator
و الـIterable
- التعامل مع الـ
Iterator
- المرور على قيم الـ
Iterator
بواسطة الحلقةfor
- أمثلة حول إنشاء
Iterator
مفهوم الـ Iterator
الـ Iterator
عبارة عن كائن يمكنه أن يحتوي على سلسة من القيم و التي يتم تخزينها فيه بالترتيب وراء بعضها البعض.
قيم هذا الكائن يمكن الوصول إليها الواحدة تلو الأخرى بنفس الترتيب الذي تم تخزينهم فيه، مما يعني أنه في كل مرة يمكن الوصول للقيمة التالية الموجودة فيه.
الـ Iterator
في بايثون عبارة عن كائن يطبق بروتوكول المكرر ( Iterator Protocol ). هذا البروتوكول يتم تطبيقه داخلياً من خلال إستدعاء الدالة __iter__()
و الدالة __next__()
المخصصتين لذلك، مما يعني أنه يمكننا إنشاء Iterator
إذا فعلنا Override لهاتين الدالتين بشكل صحيح.
الفرق بين الـ Iterator
و الـ Iterable
عند إنشاء كائن list
، tuple
، set
، أو dict
فإنك بذلك تنشئ كائن نوعه Iterable
حيث أنك تستطيع الوصول لقيمة أي عنصر فيهم بشكل مباشر من خلال ذكر رقم الـ Index أو الـ Key الخاص بالعنصر.
كائن الـ Iterator
لا يمكن الوصول لقيم محددة فيه، لأنه كما سبق و قلنا يمكنك الوصول لقيمه بنفس الترتيب الذي تم تخزينهم فيه.
في كل مرة تريد فيها الحصول على القيمة التالية فيه سيكون عليك إستدعاء دالة إسمها next()
.
الدالة next()
مصممة لإرجاع القيمة التالية في الـ Iterator
في كل مرة تستدعى فيها.
في حال وصلت إلى آخر قيمة موجودة فيه ثم قمنا باستدعائها من جديد فإنها سترجع الخطأ StopIteration.
إذاً، الفرق المهم بين الـ Iterable
و الـ Iterator
هو أن هذا الأخير لا يمكنه الوصول بشكل مباشر لقيمة محددة فيه.
التعامل مع الـ Iterator
للحصول على نسخة من كائن نوعه Iterable
ككائن نوعه Iterator
، نستخدم دالة جاهزة في بايثون إسمها iter()
.
في المثال التالي قمنا بإنشاء list
وضعنا فيه 3 قيم.
بعدها قمنا بإنشاء iterator
منه، و من ثم عرضنا القيم الموجودة فيه الواحدة تلو الأخرى.
مثال
# يحتوي 3 قيم my_list إسمه list هنا قمنا بإنشاء my_list = ["Banana", "Orange", "Apple"] # my_list وضعنا فيه قيم الكائن iterator هنا قمنا بإنشاء my_iterator = iter(my_list) # ثلاث مرات و عرض ما ترجعه في كل مرة next() هنا قمنا باستدعاء الدالة # my_iterator لاحظ أنها في كل مرة ترجع القيمة التالية في الكائن print(next(my_iterator)) print(next(my_iterator)) print(next(my_iterator))
النتيجة
Orange
Apple
المرور على قيم الـ Iterator
بواسطة الحلقة for
في المثال التالي قمنا بإنشاء list
وضعنا فيه 3 قيم.
بعدها قمنا باستخدام الحلقة for
لعرض القيم الموجودة فيه الواحدة تلو الأخرى.
المثال الأول
# يحتوي 3 قيم my_list إسمه list هنا قمنا بإنشاء my_list = ["Banana", "Orange", "Apple"] # بعدها سيتم عرض قيمته ،val و من ثم وضعها في المتغير my_list هنا في كل دورة سيتم جلب القيمة التالية من الكائن for val in my_list: print(val)
النتيجة
Orange
Apple
الذي عليك معرفته هنا هو أن مفسر لغة بايثون يقوم بتحويل الحلقة for
إلى كائن iterator
و على هذا الأساس فإن الحلقة ترجع لك القيمة التالية في كل دورة.
إذاً، الحلقة for
التي نستخدمها للمرور على قيم أي Iterable
كما يلي.
for element in iterable: # iterable هنا نكتب ما نريد أن نفعله مع قيم الـ
فعلياً مفسر لغة بايثون يقوم داخلياً بالتعامل مع الحلقة على النحو التالي.
# iterable من الـ iterator يقوم بإنشاء iter_obj = iter(iterable) # لا تتوقف while بعدها ينشئ حلقة while True: # iterator و في كل دورة في هذه الحلقة يحاول جلب القيمة التالية الموجودة في كائن الـ try: element = next(iter_obj) # سيتم إيقاف الحلقة عن العمل - StopIteration أي عندما يحدث خطأ نوعه - iterator بمجرد أن لا يعود هناك أي قيمة جديدة في كائن الـ except StopIteration: break
لست مضطر أن تفهم طريقة عمل الحلقة for
بشكل دقيق جداُ في هذه المرحلة و لكن يفضل أن تعرف ذلك لتفهم كيف تعمل لغة بايثون و تزيد خبرتك في التعامل معها. كما أنك ستفهم الكود الذي كتبناه الآن بسهولة في دروس لاحقة حين تتعلم كيف تعالج الأخطاء البرمجية باستخدام الكلمتين try
و except
.
في المثال التالي قمنا بالمرور على جميع أحرف النص و عرضهم واحداً تلو الآخر باستخدام الحلقة for
.
المثال الثاني
# هنا قمنا بتعريف متغير يحتوي على نص، أي يحتوي على سلسلة من الأحرف string = 'Python' # بعدها سيتم عرضه .string في كل دورة في الحلقة سيتم جلب حرف من هذا النص و تخزينه في المتغير for c in string: print(c)
النتيجة
y
t
h
o
n
بما أن الحلقة for
ترجع الحرف التالي الموجود في النص في كل دورة، فهذا يعني أن النوع str
أيضاً يعتبر Iterable
.
أمثلة حول إنشاء Iterator
في المثال التالي قمنا ببناء كلاس يعطينا Iterator
يعطينا عدد لا نهائي من القيم ( Infinite Iterator ).
المثال الأول
# __next__() و __iter__() للدالتين Override لأنه يفعل Iterator يمثل IterCounter هنا قمنا بإنشاء كلاس إسمه class IterCounter: # هي 0 IterCounter هنا قلنا أن القيمة الأولى التي سيتم إعطاؤها لأي كائن يتم إنشاؤه من الكلاس def __iter__(self): self.num = 0 return self # مضافاً إليها 1 num ستكون قيمة المتغير next() في كل مرة نستدعي فيها الدالة IterCounter هنا قلنا أن القيمة التالية التي سيرجعها الكائن الذي ننشئه من الكلاس def __next__(self): self.num += 1 return self.num # iter_counter و من ثم قمنا بتخزينه في الكائن ,iter() بواسطة الدالة iterator مع تحويله مباشرةً لـ IterCounter هنا قمنا بإنشاء كائن من الكلاس iter_counter = iter(IterCounter()) # next() في كل مرة نمرره للدالة iter_counter هنا قمنا بعرض القيمة التالية التي سيرجعها الكائن print(next(iter_counter)) print(next(iter_counter)) print(next(iter_counter)) print(next(iter_counter)) print(next(iter_counter))
النتيجة
2
3
4
5
في المثال التالي قمنا ببناء كلاس يعطينا Iterator
يعطينا القيم المضاعفة للعدد 2 مع جعل المستخدم قادر على تحديد عدد القيم الأقصى الذي سيتم إرجاعه.
المثال الثاني
class PowTwo: # يمكن تمرير رقم لها مباشرةً عند إنشاء كائن من هذا الكلاس end الخاصية def __init__(self, end=1): self.end = end # تساوي 0 next() الذي سنستخدمه لحساب كم مرة تم إستدعاء الدالة n هنا جعلنا القيمة الأولية للمتغير def __iter__(self): self.n = 1 return self # سيتم إرجاع القيمة end أصغر من قيمة الخاصية n إذا كانت قيمة المتغير ,next() هنا قلنا أنه كلما تم إستدعاء الدالة # الذي يعني أنه لم يعد هناك أي قيمة في الكائن StopIteration المضاعفة التالية، و إذا لم تكن أصغر منها سيتم إرجاع الخطأ def __next__(self): if self.n <= self.end: result = 2 ** self.n self.n += 1 return result else: raise StopIteration # next() و من ثم طباعة المضاعفات الخمسة للرقم 2 كلما تم إستدعاء الدالة end و تمرير القيمة 5 مكان الخاصية PowTwo هنا قمنا بإنشاء كائن من الكلاس for val in PowTwo(5): print(val)
النتيجة
4
8
16
32