ইটেরেটর পাইথনে একটি গুরুত্বপূর্ণ ভূমিকা পালন করে। এটি এমন একটি অবজেক্ট তৈরির অনুমতি দেয় যার ওপর ইটারেট করা যায়। এটি ফর-ইন লুপের মাধ্যমে অ্যাক্সেসযোগ্য ডেটা সংরক্ষণের একটি ভালো পদ্ধতি।
ইটেরেটরদের সমস্যা হলো একটি তৈরি করতে অনেক কোড লিখতে হয়। এটি উপকারী হলেও, একটা কাজ করার জন্য অনেক কোড লিখতে হয়। জেনারেটর এই সমস্যার সমাধান করে।
এই গাইডে, আমরা পাইথন জেনারেটর কী, কেন এটি ব্যবহার করা উচিত এবং কীভাবে পাইথনে একটি জেনারেটর ইমপ্লিমেন্ট করা যায় তা আলোচনা করব।
জেনারেটর কী?
জেনারেটর পাইথনে ইটেরেটর তৈরির একটি সহজ পদ্ধতি। এটি একটি ফাংশন যা একটি অবজেক্ট রিটার্ন করে যার ওপর আপনি ইটারেট করতে পারেন।
জেনারেটরকে প্রায়শই সিনট্যাক্টিক 슈গার বলা হয়। কারণ এটি পাইথনে নতুন ফাংশনালিটি যোগ করে না। এটি আপনার কোডকে আরও দক্ষ করে তোলে।
ইটেরেটরের মতোই, আপনি একটি জেনারেটরকে শুধু একবার ইটারেট করতে পারেন। কারণ জেনারেটর ট্র্যাক রাখে যে কতবার ইটারেট করা হয়েছে, এবং রিসেট করা যায় না।
চলুন একটি পাইথন ইটেরেটর দেখি:
class Cookies:
def __init__(self, value):
self.value = value
def __iter__(self):
return self
def __next__(self):
return self.value
এই কোড Cookies নামে একটি ইটেরেটর তৈরি করে। আপনি যখন এই ইটেরেটরের মাধ্যমে ডেটা পাস করবেন, এটি ডেটাকে একটি ইটেরেবল অবজেক্ট-এ রূপান্তর করবে। এর মানে আপনি ফর-ইন লুপ ব্যবহার করে ডেটার মধ্য দিয়ে লুপ করতে পারবেন। আপনি যদি এই কোডটি দেখে খুব বড় লাগে, তবে সেটি বড়ই।
জেনারেটর আমাদের এই কোড সংক্ষিপ্ত করতে সাহায্য করতে পারে। চলুন উপরের ইটেরেটরের জন্য একটি জেনারেটর লিখি:
def cookies(value):
while True:
yield value
আমাদের কোড আকারে কমিয়ে এসেছে। চলুন এটি কীভাবে কাজ করে তা আলোচনা করি।
একটি জেনারেটর হলো একটি ফাংশন যাতে রিটার্ন স্টেটমেন্টের বদলে yield কীওয়ার্ড থাকে। Yield স্টেটমেন্ট ফাংশন থেকে একটি ভ্যালু রিটার্ন করে।
Yield স্টেটমেন্ট এবং রিটার্ন স্টেটমেন্টের পার্থক্য হলো: রিটার্ন স্টেটমেন্ট ফাংশনটি চলতে বন্ধ করে দেয়, কিন্তু yield স্টেটমেন্ট ফাংশনটি পজ করে এবং ইটারেশন চালিয়ে যায়।
চলুন আমাদের সিম্পল জেনারেটর ফাংশনটি ইটারেট করার চেষ্টা করি:
for cookie in cookies("Raspberry"):
print(cookie)
আমাদের কোড রিটার্ন করে:
Raspberry
Raspberry
…
এই কোড প্রোগ্রাম বন্ধ না করলে চলতে থাকবে। কারণ আমরা একটি while লুপ ব্যবহার করেছি যা চিরকাল চালানো হয়। অধিকাংশ ক্ষেত্রে এটি খুব উপকারী নয়। চলুন একটি জেনারেটর লিখি যা একটি অ্যাকশন সম্পন্ন হলে থামবে।
পাইথন জেনারেটর কীভাবে লিখবেন
আমাদের একটি কুকির লিস্ট আছে যা কনসোলে প্রিন্ট করতে চাই। লিস্টটি এরকম:
["Raspberry", "Choc-Chip", "Cinnamon", "Oat"]
এগুলো কনসোলে প্রিন্ট করতে আমরা একটি সিম্পল জেনারেটর তৈরি করতে পারি। একটি নতুন পাইথন ফাইল খুলুন এবং নিচের কোড পেস্ট করুন:
def print_cookies(cookies):
length = len(cookies)
for cookie in range(0, length):
yield cookies[cookie]
আমাদের জেনারেটর নির্দিষ্ট লিস্টের প্রতিটি কুকির মধ্য দিয়ে যায় এবং প্রতিটি কুকি আলাদা করে রিটার্ন করে। এই জেনারেটর এখনো কাজ করে না। এটি ব্যবহার করতে আমাদের একটি ফর লুপ লাগবে:
cookie_list = ["Raspberry", "Choc-Chip", "Cinnamon", "Oat"]
for c in print_cookies(cookie_list):
print(c)
আমরা cookie_list নামে একটি অ্যারে ডিফাইন করেছি যেটি চারটি কুকি সংরক্ষণ করে। তারপর আমরা একটি ফর লুপ সেট আপ করেছি যা আমাদের জেনারেটর ব্যবহার করে cookie_list-এ মানগুলো ইটারেট করে।
আমাদের ফর লুপের প্রতিটি ইটারেশনে, জেনারেট করা অবজেক্ট কনসোলে প্রিন্ট হয়:
Raspberry
Choc-Chip
Cinnamon
Oat
আমরা করলাম! এই জেনারেটর ভ্যালু রিটার্ন করে যতক্ষণ না cookie_list ভেরিয়েবলের প্রতিটি ভ্যালু ইটারেট হয়ে যায়।
জেনারেটরের বিভিন্ন ইউজ কেস আছে। ধরুন, আমাদের অর্ডার প্রাইসের একটি লিস্ট আছে যার টোটাল বের করতে চাই। নিচের কোড দিয়ে এটি করা যায়:
def total_orders(orders):
length = len(orders)
total = 0
for o in range(0, length):
total += orders[o]
yield total
এই জেনারেটর একটি লিস্টের সকল অর্ডারের ভ্যালুর রানিং টোটাল রাখবে। প্রতিটি আইটেম ইটারেট হওয়ার পর, জেনারেটর অর্ডারগুলোয় বর্তমান টোটাল ভ্যালু yield করবে। চলুন আমাদের জেনারেটর ব্যবহার করে একটি ফর লুপ লিখি:
order_list = [2.30, 2.50, 1.95, 6.00, 7.50, 2.15]
for order in total_orders(order_list):
print(order)
আমাদের কোড রিটার্ন করে:
2.3
4.8
6.75
12.75
20.25
22.4
আমাদের জেনারেটর order_list ভেরিয়েবলের সকল অর্ডার ভ্যালুর কিউমুলেটিভ ভ্যালু ক্যালকুলেট করেছে। লিস্টের ওপর প্রতিটি ইটারেশন নতুন কিউমুলেটিভ ভ্যালু রিটার্ন করে।
জেনারেটর এক্সপ্রেশন কীভাবে লিখবেন
জেনারেটর ইতিমধ্যে ইটেরেটরের চেয়ে সহজে লিখা যায়। আমাদের ক্লিন কোড লিখার অভিযান এখানে থামতে হয় না, জেনারেটর এক্সপ্রেশনের فضلেই।
জেনারেটর এক্সপ্রেশন লিস্ট কমপ্রিহেনশনের মতো। জেনারেটর এক্সপ্রেশন জেনারেটরের মতো একবারে এক আইটেম প্রডিউস করে। এটি লিস্ট কমপ্রিহেনশন থেকে আলাদা, যা একবারে পুরো লিস্ট প্রডিউস করে।
চলুন আমাদের কুকি উদাহরণের জন্য একটি জেনারেটর লিখি:
cookie_list = ["Raspberry", "Choc-Chip", "Cinnamon", "Oat"] cookie_generator = (cookie for cookie in cookie_list)
আমরা cookie_list ভেরিয়েবলে কুকির একটি লিস্ট ডিফাইন করেছি। তারপর আমরা একটি জেনারেটর এক্সপ্রেশন তৈরি করি। এই এক্সপ্রেশন লিস্ট কমপ্রিহেনশনের সিনট্যাক্স ব্যবহার করে কিন্তু এক বড় পার্থক্য আছে: লিস্ট কমপ্রিহেনশন স্কোয়ার ব্র্যাকেটের মধ্যে ডিফাইন করা হয়, আর জেনারেটর রাউন্ড ব্র্যাকেটের মধ্যে।
চলুন এক্সপ্রেশনটি ইটারেট করার জন্য একটি ফর লুপ তৈরি করি:
for cookie in cookie_generator:
print(cookie)
আমাদের কোড রিটার্ন করে:
Raspberry
Choc-Chip
Cinnamon
Oat
এই জেনারেটরটির রেসপন্স আমাদের প্রথম উদাহরণের মতোই। কিন্তু সিনট্যাক্স অনেক বেশি ক্লিয়ার।
মনে রাখবেন, যখন আপনি জেনারেটর এক্সপ্রেশন ব্যবহার করে ডিক্লেয়ার করা একটি জেনারেটর এর ওপর ইটারেট করেন, তবে আপনি সেটিকে ফাংশন처럼 কল করবেন না। আমাদের cookie_generator জেনারেটর কোনো ইনপুট ভ্যালু নেয় না: এটিতে ইতিমধ্যে cookie_list লিস্টের ওপর ইটারেট করার কোড আছে।
জেনারেটর এক্সপ্রেশন সিনট্যাক্স শুধু তখন ব্যবহার করবেন যখন আপনাকে একটি সিম্পল ফাংশন выполня করার জেনারেটর লিখতে হবে। একটি লিস্টের ভ্যালু প্রিন্ট করা বা একটি লিস্টের ভ্যালুগুলোকে নির্দিষ্ট সংখ্যা দিয়ে গুণ করা এর ভালো উদাহরণ।
এটি কারণ, যদিও জেনারেটর এক্সপ্রেশন সিনট্যাক্স ক্লিয়ার, এটি মূলত এক-লাইন এক্সপ্রেশনের জন্য ডিজাইন করা। যদি আপনার জেনারেটর একাধিক লাইন কোড ব্যবহার করে, তবে আগে আলোচনা করা জেনারেটর ফাংশন সিনট্যাক্স ব্যবহার করুন।
জেনারেটর কেন ব্যবহার করা হয়?
জেনারেটর ব্যবহারের প্রধান কারণ হলো এটি ইটেরেটরের চেয়ে আরও সংক্ষিপ্ত।
জেনারেটর একটি ফাংশনের মতো এবং এটি কাজ করার জন্য কোনো __init__, __iter__, বা __next__ স্টেটমেন্ট দরকার নয়। এটি একটি ইটেরেটরের বিপরীতে যেটি সেই তিনটি স্টেটমেন্টই রিকোয়ার্ড। এই আচরণের কারণে আপনি একটি ইটেরেটর লিখতে থাকলে যত লাইন কোড লিখতে হতেন, তার চেয়ে অনেক কম লাইনে জেনারেটর ইমপ্লিমেন্ট করতে পারেন।
অधিকন্তু, জেনারেটর মেমরি ব্যবহারে দক্ষ। জেনারেটর একবারে এক আইটেম প্রডিউস করে। সেটি রেজাল্ট রিটার্ন করার আগে পুরো সিকোয়েন্স মেমরিতে তৈরি করে না। এই কারণে জেনারেটর বড় লিস্টের ওপর ইটারেট করার জন্য খুব প্র্যাক্টিক্যাল।
এটি কীভাবে কাজ করে সেটি হলো ইটেরেটর লেজি ইভ্যালুয়েশন মেথড ব্যবহার করে। সেগুলো শুধু তখন একটি ইটেরেবল অবজেক্টের পরবর্তী এলিমেন্ট জেনারেট করে যখন সেটি রিকোয়েস্ট করা হয়।
উপসংহার (এবং চ্যালেঞ্জ)
জেনারেটর আপনাকে আরও পাইথনিক ইটেরেটর তৈরির অনুমতি দেয়।
জেনারেটর __init__, __iter__, বা __next__ স্টেটমেন্ট লিখা ছাড়া ইটেরেটর প্রোটোকল ব্যবহার করে অবজেক্ট লিখা সহজ করে দেয়। জেনারেটর ফাংশন হিসেবে ডিফাইন করা হয়। সেগুলো yield স্টেটমেন্ট ব্যবহার করে একটি জেনারেটর থামিয়ে মেন প্রোগ্রামে একটি ভ্যালু পাস করে এবং তারপরে তার সাধারণ অপারেশন-এ ফিরে যায়।
আপনি যদি চ্যালেঞ্জ খুঁজছেন, তবে নিচে কিছু আইডিয়া দেওয়া হলো:
- একটি স্ট্রিং রিভার্স করার জেনারেটর লিখুন।
- একটি লিস্ট থেকে শুধু স펙িফিক শব্দ ধারণকারী ভ্যালু রিটার্ন করার জেনারেটর লিখুন।
- একটি লিস্টের প্রতিটি সংখ্যা দুগুন করার জেনারেটর লিখুন।
আরও জানতে, পাইথন ইটেরেটর লেখা এবং ব্যবহার করার টিউটোরিয়াল দেখুন। এখন আপনি পাইথনে জেনারেটর নিয়ে এক্সপার্টের মতো কাজ শুরু করতে পারেন!