Generator

Generator (مولد) تابعی است که می تواند متوقف شود و سپس به حالت عادی بازگردد. این تابع یک شیء از مقادیر قابل پیمایش بر می گرداند. به عبارت دیگر می توان گفت که Generator، یک نوع قابل شمارش، همانند لیست ها است که مجموعه ای از مقادیر قابل شمارش را در اختیار ما می گذارد. برای ایجاد یک Generator کافیست که یک تابع عادی تعریف کرده و فقط به جای دستور return از دستور yield استفاده کنید :

def myGenerator():
    #Code to execute
    yield something

یکی از تفاوت های یک تابع عادی با یک Generator این است که تابع عادی فقط می تواند دارای یک دستور return باشد و در نتیجه یک مقدار را بیشتر نمی تواند برگشت دهد ولی یک Generator می تواند دارای چند دستور yield و در نتیجه چند خروجی باشد. تفاوت دیگر Generator با یک تابع عادی این است که وقتی ما یک تابع عادی را صدا می زنیم، کدها از ابتدا تا انتها اجرا می شوند ولی Generator حالت قبلی خود را ذخیره و در فراخوانی های بعدی از آن حالت به بعد را اجرا می کند.
چون Generator یک نوع قابل پیمایش بر می گرداند در نتیجه می توان از حلقه ها برای به دست آوردن مقادیر آن استفاده کرد. به مثال زیر توجه کنید

 1: def counter():
 2:     i=1
 3:     while(i<=10):
 4:         yield i
 5:         i+=1
 6: 
 7: print(counter())
<generator object counter at 0x00517B70>

در کد بالا، یک تابع با نام ()counter ایجاد کرده و سپس در داخل آن یک حلقه برای برگرداندن مقادیر 10-1 نوشته ایم. این مقادیر را به جای دستور return با دستور yield برگشت می دهیم. همانطور که مشاهده می کنید، با چاپ تابع ()counter نوع آن در خروجی نمایش داده می شود که همان Generator است. حال چون این تابع از نوع Generator است، می توانیم مقادیر داخل آن را به استفاده از حلقه for به صورت زیر چاپ کنیم:

 1: def counter():
 2:     i=1
 3:     while(i<=10):
 4:         yield i
 5:         i+=1
 6: 
 7: for i in counter():
 8:     print(i)
1
2
3
4
5
6
7
8
9
10

همانطور که گفته شد در کد بالا یک Generator داریم که اعداد 10-1 تا تولید می کند و در داخل حلقه while هر با مقدار i را یک واحد اضافه کرده و این کار را تا عدد 10 ادامه می دهیم. یعنی هر بار مقدار i یک واحد بیشتر از مقدار قبلی می شود. سپس مقادیر تولید شده توسط Generator را به حلقه for می دهیم تا چاپ شوند. وجود دستور yield باعث می شود که با هر درخواست، تابع از اول اجرا نشود. یعنی وضعیت را تشخیص می دهد. بدین صورت که خطوط 3-1 یک بار اجرا می شوند.

وقتی که اولین بار در خط 7، حلقه اجرا می شود، خطوط 5-1 اجرا شده و مقدار 1 تولید و در خط 8 چاپ می شود. وقتی که برای بار دوم حلقه اجرا می شود، دیگر خطوط 3-1 اجرا نمی شوند بلکه برنامه به خط 4 رفته و آن را اجرا می کند. این بدین معنی است که دستور yield وضعیت برنامه را ذخیره کرده و می داند که در درخواست بعد باید آیتم بعدی Generator را برگرداند. این کار باعث صرفه جویی در حافظه می شود.

همانطور که گفته شد، Generator ها یک شیء قابل پیمایش بر می گردانند، در نتیجه می توان با استفاده از تابع ()next به تک تک آیتم های این شیء دسترسی پیدا کرد. به کد زیر توجه کنید:

 1: def myGenerator(number):
 2:     yield number
 3:     yield number + 1
 4: 
 5: generator = myGenerator(6)
 6: 
 7: print(next(generator))
 8: print(next(generator))
6
7

در فراخوانی اول تابع ()next در خط 7، ابتدا خطوط 1 و 2 اجرا شده و در فراخوانی دوم تابع ()next برنامه خط 3 را اجرا می کند. همانطور که در کد بالا احتمالا متوجه شده اید یک Generator می تواند دارای چندین دستور yield باشد. اگر بعد از خط 9 یک بار دیگر دستور print(next(generator)) را بنویسیم، با خطای StopIteration مواجه می شویم. به جای خطوط 8-7 می توان از دستور for هم به صورت زیر استفاده کرد:

for n in myGenerator(6):
    print(n)

این بدین معنی است که حلقه for در پس زمینه، تابع ()next را فراخوانی می کند.