Kodax

Iterators — Python المتقدم

المكرّرات (Iterators)

حلقة for تعمل مع القوائم والنصوص والقواميس والملفات... كيف تعرف Python المرور على كل هذه الأنواع المختلفة؟ الجواب: بروتوكول التكرار.

مصطلحان

- iterable (قابل للتكرار): كائن يمكن المرور عليه: قائمة، نص، قاموس، ملف. فيه الدالة __iter__.

- iterator (مكرّر): كائن يعطي العنصر التالي عند كل طلب، ويتذكر موقعه. فيه __next__.

nums = [10, 20, 30]      # iterable
it = iter(nums)          # نحصل على iterator
print(next(it))          # 10
print(next(it))          # 20
print(next(it))          # 30
next(it)                 # StopIteration: انتهت العناصر

ما تفعله for فعلياً

nums = [10, 20, 30]
it = iter(nums)
while True:
    try:
        item = next(it)
    except StopIteration:
        break
    print(item)

المكرّر يُستهلك مرة واحدة

it = iter([1, 2, 3])
print(list(it))   # [1, 2, 3]
print(list(it))   # [] انتهى

القائمة يمكن المرور عليها مراراً لأن كل for تطلب مكرّراً جديداً منها، أما المكرّر نفسه فلا يعود للبداية.

صنع مكرّر خاص

class Countdown:
    def __init__(self, start):
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

لماذا هذا مهم؟

المكرّر لا يحتاج كل العناصر في الذاكرة: يحسب العنصر التالي عند الطلب فقط. يمكنك المرور على ملف حجمه 10 جيجابايت سطراً سطراً، أو على تسلسل لا نهائي. هذا ما يسمى التقييم الكسول (lazy evaluation).

أدوات جاهزة: itertools

| الدالة | الوظيفة |

|---|---|

| count(start) | أرقام لا نهائية |

| islice(it, n) | أول n عناصر من أي مكرّر |

| chain(a, b) | المرور على عدة مصادر كأنها واحد |

| groupby(it, key) | تجميع العناصر المتتالية المتشابهة |

| zip، enumerate، map، filter | كلها ترجع مكرّرات كسولة |

تمرين

اكتب صنف EvenNumbers(limit) يعطي الأعداد الزوجية حتى limit، ثم استخدم itertools.islice و itertools.count لطباعة أول 5 مضاعفات للرقم 7.

# فهم الـ iteration
my_list = [1, 2, 3]
my_iter = iter(my_list)  # تحويل لـ iterator

print(next(my_iter))  # 1
print(next(my_iter))  # 2
print(next(my_iter))  # 3
# print(next(my_iter))  # StopIteration!

# إنشاء Iterator خاص
class CountDown:
    def __init__(self, start):
        self.current = start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

for num in CountDown(5):
    print(num)  # 5, 4, 3, 2, 1

# Iterator لانهائي
class InfiniteCounter:
    def __init__(self, start=0, step=1):
        self.current = start
        self.step = step
    
    def __iter__(self):
        return self
    
    def __next__(self):
        value = self.current
        self.current += self.step
        return value

counter = InfiniteCounter(start=10, step=5)
for _ in range(5):
    print(next(counter))  # 10, 15, 20, 25, 30

# Iterator على class مخصص
class Sentence:
    def __init__(self, text):
        self.words = text.split()
    
    def __iter__(self):
        return SentenceIterator(self.words)

class SentenceIterator:
    def __init__(self, words):
        self.words = words
        self.index = 0
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.index >= len(self.words):
            raise StopIteration
        word = self.words[self.index]
        self.index += 1
        return word

sentence = Sentence("مرحباً بك في Python")
for word in sentence:
    print(word)

# أدوات itertools
from itertools import count, cycle, repeat, chain, islice

# count: عداد لانهائي
for i in islice(count(10), 5):
    print(i)  # 10, 11, 12, 13, 14

# cycle: تكرار لانهائي
colors = cycle(['أحمر', 'أخضر', 'أزرق'])
for _ in range(5):
    print(next(colors))

# chain: دمج iterables
for x in chain([1, 2], [3, 4], [5]):
    print(x)  # 1, 2, 3, 4, 5
اختبر فهمك