Generator ها

فانکشنی که یک مقدار برمیگردونه تمام محاسباتش رو انجام میده و کنترل برنامه رو به کال کنندش برمیگردونه که مقدار ورودی رو بهش داده. با این حال این تنها رفتار ممکن یک فانکشن نیست. میتونه بجای برگردوندن یک مقدار اون رو yield کنه، که با این کار کنترل برنامه (و مقدار) رو به کال کننده میده اما وضعیت فانکشن رو در همون حالت نگه میداره و ازش نمیاد بیرون. بعدش میتونه یه مقدار دیگه yield کنه یا در نهایت اون رو برگردونه تا نشون بده که کارش تموم شده. به فانکشنی که مقداری رو yield میکنه generator گفته میشه.

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

یک مثال واقعی از وقتی که جنریتور ها میتونن به ما کمک کنن موقع سروکار داشتن با I/O هست. stream / زنجیره ای از دیتای در حال دریافت شدن از یک سرویس شبکه ای رو میشه به کمک یک جنریتور هندل کرد که دیتایی که تا الان دریافت شده و در دسترس هست رو تا زمان بسته شدن استریم yield میکنه و پس از بسته شدنش بقیه دیتای باقی مونده رو برمیگردونه. استفاده از یک جنریتور به برنامه اجازه میده تا کنترلش رو زمانی که دیتا حاضر میشه به استریم I/O و برای پردازش دیتا به کال کننده بده.

خود پایتون به صورت پیش فرض فانکشن های جنریتور رو به آبجکت هایی تبدیل میکنه که از پروتکل iterator (مثل __iter__ و __next__ و ارور StopIteration) استفاده میکنن، بنابراین دانسته هایی که در مورد iterator ها در قسمت های قبلی به دست آوردیم یعنی اینکه در مورد نحوه کار اونها اطلاع داریم. چیزی نیست که بشه توی جنریتور نوشتش اما نشه توی یک آبجکت iterator پیاده سازیش کرد. با این حال بعضی موقع ها نوشتن و درک نحوه کار یک جنریتور میتونه راحت تر باشه و نوشتن کدی که قابل درک باشه یکی از ویژگی های اصلی پایتونی بودن اون هست.