جنریتور ها (Generators)
«جنریتور (Generator)» یک «تابع (Function)» است که می تواند مکث کند و ادامه دهد. وقتی تابع را صدا می زنی، فوراً اجرا نمی شود. اول فقط آماده می شود. سپس هنگام پیمایش، قدم به قدم اجرا می شود.
آشنایی سریع با جنریتور
جنریتور شیئی «ایتریتر (Iterator)» برمی گرداند. یعنی می توانی با حلقه رویش بچرخی. به جای return از «yield» استفاده می کند.
def my_generator():
yield 1
yield 2
yield 3
for value in my_generator():
print(value)
نکته: جنریتورها داده ها را بدون نگهداری کامل در حافظه می دهند.
کلیدواژه yield
yield باعث مکث تابع می شود. وضعیت تابع ذخیره می شود. بار بعدی از همان جا ادامه می دهد.
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
for num in count_up_to(5):
print(num)
تفاوت مهم: return تابع را تمام می کند؛ yield فقط مکث می کند.
صرفه جویی در حافظه
برای داده های بزرگ، جنریتورها عالی اند. مقدارها را لحظه ای می سازند.
def large_sequence(n):
for i in range(n):
yield i
# اینجا یک میلیون عدد در حافظه ساخته نمی شود
gen = large_sequence(1000000)
print(next(gen))
print(next(gen))
print(next(gen))
تابع next و پایان داده ها
با next می توانی دستی جلو بروی. وقتی مقدار تمام شد، «StopIteration» رخ می دهد.
def simple_gen():
yield "Emil"
yield "Tobias"
yield "Linus"
gen = simple_gen()
print(next(gen))
print(next(gen))
print(next(gen))
def simple_gen():
yield 1
yield 2
gen = simple_gen()
print(next(gen))
print(next(gen))
print(next(gen)) # اینجا StopIteration رخ می دهد
عبارت های جنریتوری
شبیه «فهرست سازی (List Comprehension)» هستند؛ اما با پرانتز. نتیجه یک جنریتور است.
# لیست می سازد
list_comp = [x * x for x in range(5)]
print(list_comp)
# جنریتور می سازد
gen_exp = (x * x for x in range(5))
print(gen_exp)
print(list(gen_exp))
# محاسبه جمع مربع ها بدون ساختن لیست
total = sum(x * x for x in range(10))
print(total)
فیبوناچی بی نهایت ساز
می توانی فیبوناچی را بی پایان بسازی. حافظه هم پر نمی شود.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# صد عدد اول فیبوناچی
gen = fibonacci()
for _ in range(100):
print(next(gen))
متدهای ویژه جنریتور
send()
با send مقدار به جنریتور می فرستی. جنریتور همان را دریافت می کند.
def echo_generator():
while True:
received = yield
print("Received:", received)
gen = echo_generator()
next(gen)
gen.send("Hello")
gen.send("World")
close()
با close جنریتور را می بندی. finally می تواند پیام پایان بدهد.
def my_gen():
try:
yield 1
yield 2
yield 3
finally:
print("Generator closed")
gen = my_gen()
print(next(gen))
gen.close()
گام های عملی
- یک جنریتور ساده بساز و پیمایش کن.
- با next جلو برو و پایان را ببین.
- یک عبارت جنریتوری بنویس و جمع بگیر.
- فیبوناچی بی نهایت را پیاده سازی کن.
جمع بندی سریع
- yield اجرا را مکث می کند.
- حافظه بسیار کم مصرف می شود.
- next برای خواندنِ دستی است.
- send و close کنترل پیشرفته می دهند.
نکته: برای مقایسه روش ها، صفحه بازگشت را ببین. سپس برای شمارش عددی، range را بررسی کن.