একটি generator হলো এমন একটি ফাংশন বা এক্সপ্রেশন, যা এক বিশেষ ধরনের ইটারেটর রিটার্ন করে, যাকে বলা হয় generator iterator।
Generator-iterators হলো লেজি: এরা তাদের values মেমোরিতে জমা রাখে না, বরং দরকার হলে তাদের মান জেনারেট করে।
একটি জেনারেটর ফাংশন দেখতে অন্য যেকোনো ফাংশনের মতোই, তবে এতে এক বা একাধিক yield এক্সপ্রেশন থাকে।
প্রতিটি yield কোড এক্সিকিউশন সাময়িকভাবে স্থগিত করে, বর্তমান এক্সিকিউশন অবস্থা সংরক্ষণ করে (স্থানীয় সব ভ্যারিয়েবল ও try-স্টেটমেন্টসহ)।
জেনারেটর আবার চালু হলে, যে অবস্থায় থেমেছিল সেখান থেকেই শুরু করে। সাধারণ ফাংশনের মতো নয়, যেগুলো প্রতিবার কল করলে রিসেট হয়ে যায়।
জেনারেটর তৈরি করা হয় অন্য লুপিং বা রিকার্সিভ ফাংশনের মতোই, তবে এতে একটি yield এক্সপ্রেশন লাগে, যা আমরা একটু পরে বিস্তারিত দেখব।
একটি উদাহরণ হলো এমন একটি ফাংশন, যা সংখ্যার একটি নির্দিষ্ট অ্যারে থেকে বর্গ রিটার্ন করে। বর্তমানে যেভাবে লেখা আছে, সেখানে কোনো মান রিটার্ন করার আগে সব ইনপুট প্রসেস করতে হয়:
>>> def squares(list_of_numbers):
... squares = []
... for number in list_of_numbers:
... squares.append(number ** 2)
... return squares
আপনি ওই ফাংশনটিকে এভাবে একটি জেনারেটরে রূপান্তর করতে পারেন:
>>> def squares_generator(list_of_numbers):
... for number in list_of_numbers:
... yield number ** 2
এর পেছনের যুক্তি হলো, যখন আপনার সব মান একসাথে তৈরি করার দরকার নেই, তখন আপনি জেনারেটর ব্যবহার করেন। এতে মেমোরি ও প্রসেসিং পাওয়ার বাঁচে, কারণ শুধু যে মানটি নিয়ে আপনি এই মুহূর্তে কাজ করছেন সেটিই হিসাব করা হয়।
পাইথনে বেশিরভাগ iterables-এর জায়গায় জেনারেটর ব্যবহার করা যায়।
এর মধ্যে আছে সেইসব ফাংশন বা অবজেক্ট, যেগুলো আর্গুমেন্ট হিসেবে একটি iterable/iterator চায়।
squares_generator() জেনারেটরটি ব্যবহার করতে:
>>> squared_numbers = squares_generator([1, 2, 3, 4])
>>> for square in squared_numbers:
... print(square)
...
1
4
9
16
একটি generator-এর ভেতরের মানও next() ফাংশনের মাধ্যমে তৈরি বা অ্যাক্সেস করা যায়।
next() একটি জেনারেটর-ইটারেটর অবজেক্টের __next__() মেথড কল করে, কোডটিকে তার yield এক্সপ্রেশন পর্যন্ত "এগিয়ে নেয়" বা মূল্যায়ন করে, এরপর সেটি একটি মান "ইল্ড" বা রিটার্ন করে:
>>> squared_numbers = squares_generator([1, 2])
>>> next(squared_numbers)
1
>>> next(squared_numbers)
4
একটি generator-iterator সম্পূর্ণভাবে ব্যবহৃত হয়ে গেলে এবং রিটার্ন করার মতো আর কোনো মান না থাকলে, এটি একটি StopIteration এরর ছোড়ে।
>>> next(squared_numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
জেনারেটর-ইটারেটর হলো ইটারেটর-এর একটি বিশেষ উপসেট।
Iterators হলো সেই মেকানিজম বা প্রোটোকল, যা ইটারেবল-গুলোর উপর লুপ করা সম্ভব করে।
জেনারেটর-ইটারেটর আর সাধারণ পাইথন iterables থেকে পাওয়া ইটারেটর প্রায় একই রকম আচরণ করে, তবে কিছু গুরুত্বপূর্ণ পার্থক্য লক্ষ করার মতো:
indexes থাকে না।
যোগ বা বিয়োগ করে আগের বা পরের কোনো মান রেফারেন্স করা যায় না, আর বন্ধনী ([]) নোটেশন বা স্লাইসিংও ব্যবহার করা যায় না।len() ফাংশনের সাথে এগুলো ব্যবহার করা যায় না, কারণ এগুলোর কোনো দৈর্ঘ্য নেই।generator-iterator থেকে সব মান সংগ্রহ করার সময় সাবধান থাকুন!yield এক্সপ্রেশন আর return এক্সপ্রেশনের সাথে বেশ মিল।
return এক্সপ্রেশনের বিপরীতে, yield একটি নির্দিষ্ট বিন্দুতে কলারকে মান দিয়ে দেয়, আর বাকি মানগুলোর মূল্যায়ন বা রিটার্ন স্থগিত রাখে যতক্ষণ না সেগুলো চাওয়া হয়।
yield মূল্যায়িত হলে, এটি যে ফাংশনের ভেতরে আছে তার এক্সিকিউশন থামিয়ে দেয় এবং সেই সময়ে ফাংশনের যেকোনো মান রিটার্ন করে।
এরপর ফাংশনটি স্কোপে থেকে যায়, এবং __next__() কল করা হলে আবার yield-এর দেখা না পাওয়া পর্যন্ত এক্সিকিউশন চলতে থাকে।
ফাংশনের বাইরে yield এক্সপ্রেশন ব্যবহার করা নিষিদ্ধ।
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
>>> lets_try = infinite_sequence()
>>> lets_try.__next__()
0
>>> lets_try.__next__()
1
জেনারেটর অনেক ধরনের কাজে দরকারি।
সম্ভাব্য বিশাল মানের সংগ্রহ নিয়ে কাজ করার সময়, আপনি হয়তো সেগুলোর সবগুলো মেমোরিতে রাখতে চাইবেন না। বড় ডেটার উপর টুকরো টুকরো কাজ করতে জেনারেটর ব্যবহার করা যায়, যা মেমোরি বাঁচায় ও পারফরম্যান্স বাড়ায়।
কোনো প্রক্রিয়া বা হিসাব জটিল, ব্যয়বহুল বা অসীম হলে জেনারেটর খুবই সহায়ক:
>>> def infinite_sequence():
... current_number = 0
... while True:
... yield current_number
... current_number += 1
এখন infinite_sequence অবজেক্টে যখনই __next__() কল করা হয়, এটি আগের সংখ্যা + 1 রিটার্ন করবে।
কন্ডা এয়ারলাইনস প্রোগ্রামিং জগতের সবচেয়ে বড় এয়ারলাইন, দিনে ১০,০০০-এরও বেশি ফ্লাইট চলে!
এখন তারা হাতে করে সব সিট যাত্রীদের মধ্যে অ্যাসাইন করছেন; এটিকে স্বয়ংক্রিয় করতে হবে।
তারা আপনাকে এমন একটি সফটওয়্যার তৈরি করতে বলেছে, যা যাত্রীদের সিট অ্যাসাইনমেন্ট স্বয়ংক্রিয় করবে। তাদের চাওয়া, আপনার সফটওয়্যার হবে মেমরি সাশ্রয়ী ও পারফরম্যান্ট।
কন্ডা তাদের বিমানের জন্য সিট লেটার জেনারেট করতে চায়।
একটি বিমান সারি সারি সিট দিয়ে তৈরি।
প্রতিটি সারিতে ৪টি সিট থাকে।
প্রতিটি সারির সিটগুলোর নাম সবসময় A, B, C এবং D।
সারির প্রথম সিটটি A, দ্বিতীয়টি B, এভাবেই চলতে থাকে।
D-তে পৌঁছানোর পর আবার A দিয়ে শুরু হবে।
এমন একটি ফাংশন generate_seat_letters(<number>) ইমপ্লিমেন্ট করুন, যা একটি int নেয় যাতে থাকে কতটি সিট লেটার জেনারেট করতে হবে।
এরপর ফাংশনটি সিট লেটারগুলোর একটি ইটারেবল রিটার্ন করবে।
>>> letters = generate_seat_letters(4)
>>> next(letters)
"A"
>>> next(letters)
"B"
কন্ডা এমন একটি সিস্টেম চায়, যা তাদের বিমানের জন্য নির্দিষ্ট সংখ্যক সিট জেনারেট করতে পারে।
প্রতিটি বিমানের প্রতিটি সারিতে ৪টি সিট থাকে।
সারিগুলো সংখ্যা দিয়ে চিহ্নিত হয়, 1 থেকে শুরু করে ক্রমশ বাড়তে থাকে।
সিটগুলো এভাবে সাজানো থাকবে: 1A, 1B, 1C, 1D, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, ...
এখানে একটি উদাহরণ:
| x | 1 | 2 |
|---|---|---|
| সারি | 5 | 21 |
| সিট লেটার | A | D |
| ফলাফল | 5A | 21D |
যাত্রীদের কুসংস্কারের কারণে বহু এয়ারলাইন তাদের ফ্লাইটে সারি নম্বর ১৩ রাখে না। কন্ডা এয়ারলাইনসও এই রীতি মেনে চলে, তাই খেয়াল রাখুন, সারি নম্বর ১৩-এর জন্য সিট জেনারেট করবেন না।
এমন একটি ফাংশন generate_seats(<number>) ইমপ্লিমেন্ট করুন, যা একটি int নেয় যাতে থাকে কতটি সিট জেনারেট করতে হবে।
এরপর ফাংশনটি প্রদত্ত সিটগুলোর একটি ইটারেবল রিটার্ন করবে।
>>> seats = generate_seats(10)
>>> next(seats)
"1A"
>>> next(seats)
"1B"
এখন যেহেতু সিট জেনারেট করার একটি ফাংশন আপনার কাছে আছে, এটি দিয়েই আপনি যাত্রীদের সিট অ্যাসাইন করতে পারেন।
এমন একটি ফাংশন assign_seats(<passengers>) ইমপ্লিমেন্ট করুন, যা যাত্রীদের নামের একটি list নেয়।
এরপর ফাংশনটি একটি ডিকশনারি রিটার্ন করবে, যেখানে passenger থাকে কী (key) হিসেবে এবং seat_number থাকে মান হিসেবে।
>>> passengers = ['Jerimiah', 'Eric', 'Bethany', 'Byte', 'SqueekyBoots', 'Bob']
>>> assign_seats(passengers)
{'Jerimiah': '1A', 'Eric': '1B', 'Bethany': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
কন্ডা এয়ারলাইনস প্রতিটি টিকিটের জন্য একটি অনন্য কোড চায়। যেহেতু তারা একটি বড় এয়ারলাইন, তাদের অনেক ফ্লাইট আছে। এর মানে, একই সিট নম্বরের একাধিক ফ্লাইট থাকতে পারে। তারা চায় আপনি এমন একটি সিস্টেম তৈরি করুন, যা একটি অনন্য টিকিট গঠন করে, আর সেই টিকিটটি ১২ ক্যারেক্টার লম্বা একটি স্ট্রিং।
এই কোডটি শুরু হয় assigned_seat দিয়ে, আর তার পরেই থাকে flight_id।
কোডের বাকি অংশ 0s দিয়ে ভরাট করা হয়।
এমন একটি ফাংশন generate_codes(<seat_numbers>, <flight_id>) ইমপ্লিমেন্ট করুন, যা seat_numbers-এর একটি list এবং ফ্লাইট নম্বরসহ একটি string নেয়।
এরপর ফাংশনটি একটি generator রিটার্ন করবে, যা একটি ticket_number দেয়।
>>> seat_numbers = ['1A', '17D']
>>> flight_id = 'CO1234'
>>> ticket_ids = generate_codes(seat_numbers, flight_id)
>>> next(ticket_ids)
'1ACO12340000'
>>> next(ticket_ids)
'17DCO1234000'
Exercism-এ সাইন আপ করুন, Python ট্র্যাকের 17টি কনসেপ্ট146টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।