افزایش سرعت اجرای کد پایتون با Generator (جنراتورها)
جنراتورها : تعریف و کاربرد
اغلب افرادی که با Generator-های پایتون آشنایی ندارند یا به تازگی آشنا شدهاند، در مورد اهمیت این مفهوم دچار تردید می شوند. Generator-ها به طور خاص در زمینه یادگیری ماشین برای نوشتن تابعهای سفارشی نقش بسیار مهمی ایفا میکنند. در این مقاله با روش کاهش مصرف حافظه و افزایش سرعت اجرای کد پایتون با Generator آشنا خواهیم شد. جهت مطالعه مقاله کامل به ادامه مطلب رجوع کنید.
تابعهای Generator این امکان را فراهم میسازند که تابعی اعلان کنیم که مانند یک تکرارکننده (iterator) عمل میکند. این نوع از تابعها به برنامهنویس امکان میدهند یک تکرارکننده را به روشی سریع، آسان و تمیز ایجاد کند. منظور از تکرارکننده شیئی است که میتواند (در یک حلقه) تکرار شود. از تکرارکننده برای تجرید یک کانتینر دادهها استفاده میشود تا طوری رفتار کند که گویی یک شیء تکرارپذیر است. برخی از موارد رایج از اشیای تکرارپذیر شامل رشتهها، لیستها و دیکشنریها هستند. Generator تا حد زیادی شبیه یک تابع است، اما به جای return از کلیدواژه yield استفاده میکند. برای درک بهتر یک مثال را بررسی میکنیم:
def generate_numbers(): n = 0 while n < 3: yield n n += 1
این یک تابع generator است و زمانی که آن را فراخوانی کنیم، یک شیء generator بازگشت میدهد:
1>>> numbers = generate_numbers() 2>>> type(numbers) 3<class 'generator'
نکته مهمی که باید توجه داشته باشیم این است که حالت (State) درون بدنه تابع generator قرار گرفته است. امکان حرکت به صورت گام به گام با استفاده از تابع داخلی next() نیز وجود دارد:
>>> next_number = generate_numbers() >>> next(next_number) 0 >>> next(next_number) 1 >>> next(next_number) 2
فراخوانی ()next پس از رسیدن به انتها
StopIteration یک نوع استثنای داخلی است که به صورت خودکار در مواردی که generator نتواند yield کند صادر میشود. این نشانهای از این است که حلقه باید متوقف شود.
گزاره yield
وظیفه اصلی گزاره yield کنترل گردش تابع generator به روشی است که مانند گزاره return به نظر رسد. هنگامی که یک تابع generator را فرا میخوانید یا از یک عبارت generator استفاده میکنید، یک تکرارکننده خاص به نام generator دریافت میشود. میتوان این generator را به یک متغیر انتساب داد تا از آن استفاده کند. زمانی که متدهای خاص روی generator از قبیل ()next را فراخوانی میکنید، کد درون تابع تا yield اجرا میشود.
هنگامی که پایتون با گزاره yield مواجه شود، برنامه اجرای تابع را معلق میکند و مقدار yield شده را به فراخوانی کننده بازگشت میدهد. این وضعیت مخالف گزاره return در پایتون است که اجرای تابع را به طور کامل متوقف میکند. زمانی که یک تابع تعلیق شود، حالت تابع ذخیره میشود. اکنون که با generator پایتون آشنا شدید، نوبت آن رسیده است که رویکرد معمولی را با رویکردی که از generator استفاده میکند از نظر مصرف حافظه و زمان مورد نیاز برای اجرای کدهای پایتون مقایسه کنیم.
صورت مسئله
فرض کنید مجبور هستیم روی یک لیست بزرگی از اعداد (مثلاً 100000000 عدد) حلقهای تعریف کنیم و مربع همه این اعداد را در یک لیست جداگانه ذخیره کنیم.
رویکرد معمول
import memory_profiler import time def check_even(numbers): even = [] for num in numbers: if num % 2 == 0: even.append(num*num) return even if __name__ == '__main__': m1 = memory_profiler.memory_usage() t1 = time.clock() cubes = check_even(range(100000000)) t2 = time.clock() m2 = memory_profiler.memory_usage() time_diff = t2 - t1 mem_diff = m2[0] - m1[0] print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method")
با اجرای کد فوق خروجی زیر به دست میآید:
رویکرد Generator
It took 21.876470000000005 Secs and 1929.703125 Mb to execute this method
1import memory_profiler 2import time 3def check_even(numbers): 4 for num in numbers: 5 if num % 2 == 0: 6 yield num * num 7 8if __name__ == '__main__': 9 m1 = memory_profiler.memory_usage() 10 t1 = time.clock() 11 cubes = check_even(range(100000000)) 12 t2 = time.clock() 13 m2 = memory_profiler.memory_usage() 14 time_diff = t2 - t1 15 mem_diff = m2[0] - m1[0] 16 print(f"It took {time_diff} Secs and {mem_diff} Mb to execute this method")
برای مقایسه این دو مورد جدول زیر را مشاهده کنید:
چنان که میبینید، هم مصرف حافظه و هم زمان مورد نیاز برای اجرای کد در زمان استفاده از Generator به میزان زیادی کاهش یافته است. Generator-ها تنها بنا به تقاضا کار میکنند و مشهور است که از طریق ارزیابی تنبل (lazy) عمل میکنند. این بدان معنی است که میتوانند در مصرف CPU، حافظه و دیگر منابع محاسباتی صرفهجویی کنند.
نتیجه گیری
در این بخش به جمعبندی مطالب طرح شده در این مقاله میپردازیم. در این راهنما نشان دادیم که generator-های پایتون میتوانند برای کاهش مصرف حافظه و اجرای سریعتر کدها مورد استفاده قرار گیرند. مزیت اصلی آنها در این واقعیت نهفته است که نتایج را در حافظه ذخیره نمیکنند، بلکه آنها را به صورت درجا تولید میکنند و از این رو حافظه تنها زمانی استفاده میشود که نتیجهای را از آنها طلب کنیم. همچنین generator-ها اغلب بخشهای کد از پیش آماده مورد نیاز برای نوشتن تکرارکنندهها را دور میاندازند که به کاهش اندازه کد نیز کمک میکند.
برگرفته از وبسایت آموزشی فرادرس
مثل همیشه سایت شما عالیه