পাইথনে জেনারেটরসমূহ

পাইথনে জেনারেটরসমূহ

এই নিবন্ধে পাইথনের জেনারেটর নিয়ে ব্যাখ্যা করা হয়েছে।

YouTube Video

পাইথনে জেনারেটরসমূহ

সংক্ষিপ্ত বিবরণ

পাইথনের জেনারেটর একটি বিশেষ ধরনের ইটারেটর এবং পুনরাবৃত্তিমূলক কাজ দক্ষতার সঙ্গে করার জন্য একটি শক্তিশালী ফিচার। এগুলো ব্যবহার করে বড় পরিমাণ ডেটা প্রসেস করার সময় আপনি মেমোরি-দক্ষ কোড লিখতে পারেন।

জেনারেটর কী?

পাইথনে জেনারেটর হল এমন একটি বিশেষ ফাংশন যা yield কীওয়ার্ড ব্যবহার করে একসাথে একটি করে মান প্রদান করে। এর বৈশিষ্ট্য হলো এটি নিজের অবস্থা ধরে রেখে কার্যকারিতা সাময়িকভাবে স্থগিত করতে ও পরে আবার শুরু করতে পারে।

yield-এর মৌলিক বিষয়

yield একটি কীওয়ার্ড যা একই সঙ্গে একটি মান ফেরত দেয় এবং ফাংশনের কার্যকারিতা স্থগিত করে

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8print(next(gen))  # 1
 9print(next(gen))  # 2
10print(next(gen))  # 3
  • এই ফাংশনটি কল করলে এটি একটি জেনারেটর অবজেক্ট ফেরত দেয়, যা একে একে মান প্রদান করে।
  • যদি আপনি পরবর্তী মান না থাকলে next() কল করেন, তাহলে একটি StopIteration ত্রুটি হবে।

next() এবং StopIteration

 1def simple_generator():
 2    yield 1
 3    yield 2
 4    yield 3
 5
 6gen = simple_generator()
 7
 8try:
 9    while True:
10        value = next(gen)
11        print(value)
12except StopIteration:
13    print("Finished")
  • এইভাবে স্পষ্টভাবে StopIteration ত্রুটিটি পরিচালনা করলে, আপনি জানতে পারবেন কখন একটি জেনারেটর শেষ হয়েছে।

send(value)

send(value) কল করলে জেনারেটর আবার শুরু হয় এবং value টি yield এক্সপ্রেশনের অবস্থানে পাঠানো হয়। পাঠানো মানটি জেনারেটর পাশে yield এক্সপ্রেশনের রিটার্ন মান হিসেবে গ্রহণ করা যায়। প্রথম কলের সময়, আপনি send(value) দিয়ে None ছাড়া কিছু পাঠাতে পারেন না, তাই next() অথবা send(None) ব্যবহার করতে হবে।

 1def gen():
 2    x = yield 1
 3    print(f"x = {x}")
 4    y = yield 2
 5    print(f"y = {y}")
 6
 7g = gen()
 8print(next(g))       # -> 1 (value from yield 1)
 9print(g.send(10))    # -> x = 10, 2 (value from yield 2)
10print(g.send(20))    # -> y = 20, StopIteration occurs
  • send(10) দিলে, জেনারেটরের yield একটি এক্সপ্রেশন হয়ে যায় যা ১০ রিটার্ন করে, এবং x-এর মধ্যে ১০ এসাইন হয়।

throw()

throw কল করলে জেনারেটর আবার শুরু হয় এবং থেমে থাকা yield-এর অবস্থানে একটি এক্সেপশন তোলে। আপনি জেনারেটরের ভিতরে এক্সেপশন হ্যান্ডল করতে পারেন যাতে প্রসেসিং চলতে থাকে। যদি এক্সেপশন ধরা না হয়, এটি বাইরে ছড়িয়ে পড়ে এবং জেনারেটর শেষ হয়।

 1def gen():
 2    try:
 3        yield 1
 4    except ValueError as e:
 5        print(f"Caught: {e}")
 6        yield "recovered"
 7
 8g = gen()
 9print(next(g))   # -> 1
10print(g.throw(ValueError("boom")))  # -> Caught: boom, "recovered"
  • এই কোডে, এক্সেপশন জেনারেটরের মধ্যে ঢালার জন্য throw কল করা হয়েছে। জেনারেটরের পাশে এক্সেপশনটি হ্যান্ডল করা হয় এবং recovered ফেরত দেওয়া হয়।

close()

close() কল করলে জেনারেটর শেষ (টার্মিনেট) হয়। জেনারেটরের ভিতরে, আপনি finally ব্যবহার করে ক্লিনআপ করতে পারেন। close() কল করার পরে next() বা send() কল করলে একটি StopIteration এরর ওঠে।

1def gen():
2    try:
3        yield 1
4    finally:
5        print("Cleaning up...")
6
7g = gen()
8print(next(g))  # -> 1
9g.close()       # -> Cleaning up...
  • এই কোডটি দেখায় যে close() কল করলে জেনারেটর শেষ হয় এবং finally-এর মধ্যে ক্লিনআপ প্রসেস শুরু হয়।

yield from

yield from হল এমন একটি সিনট্যাক্স যা সাবজেনারেটরে কাজ হস্তান্তরের জন্য ব্যবহৃত হয়। এটি একটি জেনারেটরের ভেতরে আরেকটি জেনারেটরকে কল করার এবং তার সমস্ত মান বাইরের স্কোপে পাঠিয়ে দেওয়ার একটি সহজ উপায়

1def sub_gen():
2    yield 1
3    yield 2
4
5def main_gen():
6    yield from sub_gen()
7    yield 3
8
9print(list(main_gen()))  # -> [1, 2, 3]
  • এই কোডটি yield from ব্যবহার করে সাবজেনারেটরের সব মান বাইরের জেনারেটরে পাঠিয়ে দেয় এবং তারপর 3 রিটার্ন করে।

ইটারেটরের সাথে সম্পর্ক

জেনারেটর অভ্যন্তরীণভাবে __iter__() এবং __next__() ইমপ্লিমেন্ট করে, ফলে এগুলো একজাতীয় ইটারেটর হয়ে ওঠে। এ কারণে, এগুলো for লুপসহ অন্যান্য ইটারেবল অপারেশনে সম্পূর্ণরূপে ব্যবহারযোগ্য।

for লুপের সঙ্গে একত্রীকরণ

পাইথনে, for লুপ অভ্যন্তরীণভাবে next() ব্যবহার করে স্বয়ংক্রিয়ভাবে মান নিয়ে আসে।

1def simple_generator():
2    yield 1
3    yield 2
4    yield 3
5
6for value in simple_generator():
7    print(value)

এই পদ্ধতিতে, StopIteration-এর হ্যান্ডলিংও স্বয়ংক্রিয়ভাবে হয়।

অনন্ত (infinite) জেনারেটর তৈরি করা

1def count_up(start=0):
2    while True:
3        yield start
4        start += 1
5
6counter = count_up()
7print(next(counter))  # 0
8print(next(counter))  # 1

অনন্ত লুপ তৈরি করা সম্ভব, তবে সেগুলো ব্যবহার করার সময় সতর্ক থাকতে হবে।

জেনারেটর এক্সপ্রেশন

ব্র্যাকেটের মধ্যে লেখা জেনারেটর এক্সপ্রেশন লিস্ট কম্প্রিহেনশনের মতোই সহজ সিনট্যাক্সে জেনারেটর সংজ্ঞায়িত করতে দেয়।

1# List comprehension (generates the entire list at once)
2squares_list = [x**2 for x in range(5)]
3print(squares_list)
4
5# Generator expression
6squares_gen = (x**2 for x in range(5))
7for square in squares_gen:
8    print(square)

লিস্ট কম্প্রিহেনশনের মতো নয়, এগুলো সমস্ত উপাদান একসঙ্গে মেমোরিতে রাখে না, ফলে বেশি মেমোরি-দক্ষ।

জেনারেটরে ত্রুটি (error) হ্যান্ডলিং

জেনারেটরের ভিতরে এক্সসেপশন (ব্যতিক্রম) ঘটতে পারে। এমন পরিস্থিতিতে, সাধারণ পাইথন কোডের মতোই try-except ব্যবহার করতে হয়।

 1def safe_divide_generator(numbers, divisor):
 2    """Yields results of dividing numbers by a given divisor safely."""
 3    for number in numbers:
 4        try:
 5            yield number / divisor  # Attempt to divide and yield result.
 6        except ZeroDivisionError:
 7            yield float('inf')  # Return infinity if division by zero occurs.
 8
 9# Example usage
10numbers = [10, 20, 30]
11gen = safe_divide_generator(numbers, 0)  # Create generator with divisor as 0.
12for value in gen:
13    print(value)  # Output: inf, inf, inf

এই উদাহরণে, শূন্য দ্বারা ভাগ করার ক্ষেত্রে সঠিক ত্রুটি ব্যবস্থাপনা করা হয়েছে।

জেনারেটরের স্ট্যাক ট্রেস

যদি জেনারেটরের ভিতরে কোনো এক্সসেপশন ঘটে, তবে জেনারেটর পুনরায় শুরু হলে সেটি উত্থাপিত হবে।

 1def error_generator():
 2    """A generator that yields values and raises an error."""
 3    yield 1
 4    raise ValueError("An error occurred")  # Raise a ValueError intentionally.
 5    yield 2
 6
 7gen = error_generator()
 8print(next(gen))       # Output: 1 (first value yielded)
 9try:
10    print(next(gen))   # Attempt to get the next value, which raises an error
11except ValueError as e:
12    print(e)           # Output: An error occurred (exception message is printed)
  • এই জেনারেটর প্রথমে 1 ফেরত দেয়। পুনরায় শুরু করার সময় উত্থাপিত ত্রুটিটি ধরা হয় এবং একটি ত্রুটি বার্তা হিসেবে প্রদর্শিত হয়।

জেনারেটর ব্যবহারের উদাহরণ

ফাইল থেকে এক লাইন করে পড়া (বিশাল ফাইলের জন্য উপযোগী)

1def read_large_file(filepath):
2    with open(filepath, 'r') as f:
3        for line in f:
4            yield line.strip()
  • এই ফাংশনটি একটি ইটারেটর ব্যবহার করে টেক্সট ফাইলটি লাইনে লাইনে পড়ে, প্রতিটি লাইনের ফাঁকা স্থান ছেঁটে দেয়, এবং সেটি জেনারেটর হিসেবে ফেরত দেয়, ফলে বড় ফাইল কম মেমরি ব্যবহারে প্রক্রিয়া করা যায়।

ফিবোনাচ্চি সিরিজের জন্য জেনারেটর

1def fibonacci(limit):
2    a, b = 0, 1
3    while a < limit:
4        yield a
5        a, b = b, a + b
6
7for n in fibonacci(100):
8    print(n)
  • এই কোডটি একটি জেনারেটর ব্যবহার করে উপরের সীমার চেয়ে ছোট ফিবোনাচ্চি সংখ্যা পর্যায়ক্রমে তৈরি করে এবং for লুপ ব্যবহার করে সেগুলি আউটপুট দেয়।

ব্যবহারের ক্ষেত্র

জেনারেটরগুলো নিচের পরিস্থিতিতেও ব্যবহার করা যেতে পারে।

  • বড় CSV বা লগ ফাইলের ক্রমিকভাবে প্রসেসিং
  • API প্যাজিনেশন
  • স্ট্রিমিং ডেটা প্রসেসিং (যেমন, Kafka, IoT ডিভাইস)

সারসংক্ষেপ

ধারণা মূল বিষয়
yield স্থগিত করে এবং মান ফেরত দেয়
জেনারেটর ফাংশন একটি ফাংশন যেটিতে yield থাকে এবং কল করলে একটি ইটারেটর ফেরত দেয়
সুবিধা মেমোরি-দক্ষ এবং বড় ডেটাসেট প্রসেস করার জন্য আদর্শ
জেনারেটর এক্সপ্রেশন (x for x in iterable) এর মতো সংক্ষিপ্ত সিনট্যাক্স ব্যবহারের সুযোগ দেয়

জেনারেটর ব্যবহার করে আপনি কম মেমোরি ব্যবহার করে এবং কোড সংক্ষিপ্ত রেখে দক্ষতার সঙ্গে বড় ডেটাসেট প্রসেস করতে পারবেন।

আপনি আমাদের ইউটিউব চ্যানেলে ভিজ্যুয়াল স্টুডিও কোড ব্যবহার করে উপরের নিবন্ধটি অনুসরণ করতে পারেন। দয়া করে ইউটিউব চ্যানেলটিও দেখুন।

YouTube Video