ইটারেটর ও জেনারেটর #
ইটারেটর #
লুপ চাপ্টারে for লুপের ভিতরে আমরা ইটারেটরের হালকা-পাতলা প্রয়োগ দেখেছিলাম। আমরা দেখেছিলাম, for লুপ দিয়ে লিস্ট, টাপল, ডিকশনারি, স্ট্রিং এসব অবজেক্টকে ইটারেট করা যায়। ইটারেট করা যায় বলে এদেরকে ইটারেবল অবজেক্ট বলে। বিল্ট-ইন ফাংশন iter()
এর ভিতর যদি আমরা কেন ইটারেবল অবজেক্টকে পাস করি তবে তা একটা ইটারেটর রিটার্ন করে। আর __next__()
মেথড দিয়ে আমরা একটা ইটারেটরের পরবর্তী এলিমেন্ট বা আইটেমকে অ্যাক্সেস করতে পারি। সবগুলো আইটেম অ্যাক্সেস করতে করতে যখন আর কোন আইটেম বাকি থাকে না তখন পাইথন StopIteration
এরর থ্রো করে। একটা উদাহরণ দেখা যাক।
>>> x = iter([1, 2, 3])
>>> x
<list_iterator object at 0x7f0f1694b1d0>
>>> x.__next__()
1
>>> x.__next__()
2
>>> x.__next__()
3
>>> x.__next__()
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
x.__next__()
StopIteration
আমরা এখন একটা ইটারেটর ক্লাস পয়দা করব। আমাদের এই ইটারেটর বিল্ট-ইন range()
ফাংশনের মত কাজ করবে, তবে রিভার্সলি। মানে range(5)
মানে তো 0, 1, 2, 3, 4। কিন্তু আমাদের তৈরি ইটারেটর ক্লাস এটাকে 5, 4, 3, 2, 1, 0 এরকম কিছু একটা বানাবে। তো চেষ্টা করা যাক।
class revrange:
def __init__(self, n):
self.n = n
self.i = n
def __iter__(self):
return self
def __next__(self):
if self.n >= 0:
if self.i == self.n:
self.n = self.n - 1
return self.i
else:
self.i = self.n
self.n = self.n - 1
return self.i
else:
raise StopIteration
for temp in revrange(5):
print(temp)
আউটপুট
5
4
3
2
1
0
__iter__()
মেথডটা কিন্তু খুব গুরুত্বপূর্ণ এখানে। এটা ছাড়া পুরো ক্লাসটাই অচল হয়ে থাকবে।
জেনারেটর #
জেনারেটর একটা ফাংশন। এই ফাংশনের কাজ হল yield
স্টেটমেন্ট ব্যবহার করে সিকুয়েন্স পয়দা করা। এইদিক থেকে জেনারেটরও এক ধরনের ইটারেটর।
def revrange(n):
while n >= 0:
yield n
n = n - 1
for temp in revrange(5):
print(temp)
আউটপুট
5
4
3
2
1
0
যখন একটা জেনারেটর ফাংশন কল করা হয় তখন এটা ফাংশনের কোড এক্সিকিউট হবার আগেই একটা জেনারেটর অবজেক্ট রিটার্ন করে। যখন প্রথমবার __next__()
মেথড কল করা হয় তখন ফাংশনের কোড এক্সিকিউট হওয়া শুরু করে। এক্সিকিউট হতে হতে যখন yield
স্টেটমেন্ট অবধি পোঁছায় তখন __next__()
মেথড ইয়েল্ডেড ভ্যালু রিটার্ন করে।