هنر نوشتن حلقه در پایتون
در بیشتر زبانهای برنامهنویسی، حلقه for کُد یا مجموعهای از دستورات را تا زمان برقراری شرط تعیینشده، تکرار میکند. برای مثال، حلقه for در زبان C بدین شکل است:
int i; for (i=0;i<N;i++) { //do something }
بهترین روش برای نوشتن حلقه for در زبان C همین است. در موارد پیچیده، اغلب مجبور میشویم حلقههایی تودرتو بنویسیم که ظاهر زیبایی ندارند یا تعداد زیادی متغیر کمکی تعریف کنیم ( همانند i در کد فوق).
خوشبختانه، نوشتن حلقه و بهطور کلی کدنویسی در زبان برنامهنویسی پایتون بسیار آسانتر است. ترفندهای زیادی برای نوشتن حلقههای بهتر وجود دارد. در زبان برنامهنویسی پایتون، مجبور نیستیم حلقههای تودرتو بنویسیم یا از متغیرهای کمکی استفاده کنیم. علاوه بر این، در پایتون میتوانیم حلقه for را شخصیسازی کنیم.
در این نوشتار، چند ترفند برای نوشتن حلقه for در پایتون را به شما معرفی میکنیم.
بازگرداندن اندیسها و مقادیر بهصورت همزمان
از جمله موارد کاربرد حلقه for میتوان به بازگرداندن اندیسها و مقادیر یک لیست اشاره کرد. زمانی که شروع به یادگیری زبان برنامهنویسی پایتون کرده بودم، کدهایی بدین شکل مینوشتم:
for i in range(len(my_list)): print(i, my_list[i])
البته این کُدها هیچ مشکلی ندارند؛ اما به اندازه کافی خوب نیستند. پس از گذشت چندین ماه متوجه شدم روش بهتری هم برای نوشتن حلقه for در پایتون وجود دارد:
for i, v in enumerate(my_list): print(i, v)
همانگونه که در کُد فوق مشاهده میکنید، تابع توکار enumerate انجام بسیاری از کارها را آسانتر میکند.
اجتناب از نوشتن حلقههای تودرتو با استفاده از تابع Product
حلقههای تودرتو علاوه بر اینکه ظاهر زیبایی ندارند، مشکلآفرین هم هستند. وجود این حلقهها، خواندن کُدها را دشوار میکند و باعث پیچیدهتر شدن برخی کارها میشوند. برای مثال، تجزیه حلقههای تودرتو به بخشهای کوچکتر کار آسانی نیست. در چنین شرایطی باید بدانیم حلقه داخلی اول، حلقه داخلی دوم و غیره چگونه شکسته و تجزیه میشوند.
خوشبختانه، ماژول توکار itertools پایتون تابع فوقالعادهای product به نام دارد. در صورت استفاده از این تابع دیگر نیاز نیست حلقههای تودرتوی زیادی بنویسیم.
به مثال زیر توجه کنید:
list_a = [1, 2020, 70] list_b = [2, 4, 7, 2000] list_c = [3, 70, 7] for a in list_a: for b in list_b: for c in list_c: if a + b + c == 2077: print(a, b, c) # 70 2000 7
همانگونه که در کُد فوق مشاهده میکنید، برای بازگرداندن اعدادی که مجموعشان برابر با 2077 است (از سه لیست) باید سه حلقه تودرتو بنویسیم. کُد فوق نظم و ترتیب ندارد.
در اینجاست که میتوانیم از تابع product کمک بگیریم:
1 from itertools import product 2 3 list_a = [1, 2020, 70] 4 list_b = [2, 4, 7, 2000] 5 list_c = [3, 70, 7] 6 7 for a, b, c in product(list_a, list_b, list_c): 8 if a + b + c == 2077: 9 print(a, b, c) 10 # 70 2000 7
در کُد فوق از تابع product استفاده کردیم و به همین دلیل فقط یک حلقه نوشتیم.
تابع product بر روی ورودیهای تکرارپذیر، ضرب دکارتی انجام میدهد. در این حالت، در بسیاری موارد نیازی به نوشتن حلقههای تودرتو نخواهد بود.
نوشتن حلقههای زیبا با استفاده از ماژول Itertools اگر نگاهی به ماژول itertools در پایتون بیندازید، متوجه میشوید تایع product یکی از هزاران توابع کاربردی این زبان است. این جعبهابزار شامل متدهای کاربردی زیادی است که کار با حلقهها را برای ما آسانتر میکنند.
ایجاد حلقه for بینهایت
حداقل سه متد برای ایجاد یک حلقه بینهایت در اختیار داریم:
- به کمک تابع count
natural_num = itertools.count(1) for n in natural_num: print(n) # 1,2,3,...
- به کمک تابع cycle
many_yang = itertools.cycle('Yang') for y in many_yang: print(y) # 'Y','a','n','g','Y','a','n','g',...
- به کمک تابع repeat
many_yang = itertools.repeat('Yang') for y in many_yang: print(y) # 'Yang','Yang',...
تبدیل چندین iterator به یک iterator واحد
تابع chain() میتواند چندین iterator را با هم ترکیب کند و بهصورت یک iterator واحد در بیاورد.
from itertools import chain list_a = [1, 22] list_b = [7, 20] list_c = [3, 70] for i in chain(list_a, list_b, list_c): print(i) # 1,22,7,20,3,70
انتخاب عناصر تکراری مجاور تابع groupby آیتمهای تکراری یک iterator را انتخاب و با یکدیگر ترکیب میکند.
from itertools import groupby for key, group in groupby('YAaANNGGG'): print(key, list(group)) # Y ['Y'] # A ['A'] # a ['a'] # A ['A'] # N ['N', 'N'] # G ['G', 'G', 'G']
همانگونه که در کُد فوق مشاهده میکنید، کاراکترهایی که مشابه یکدیگر بودند و در مجاورت یکدیگر قرار داشتند، در یک گروه قرار گرفتند. علاوه بر این، میتوانیم چگونگی تشخیص دو عنصر مشابه را برای تابع groupby مشخص کنیم (دو عنصر شبیه به هم هستند یا خیر):
from itertools import groupby for key, group in groupby('YAaANNGGG', lambda x: x.upper()): print(key, list(group)) # Y ['Y'] # A ['A', 'a', 'A'] # N ['N', 'N'] # G ['G', 'G', 'G']
شخصیسازی حلقه for
تا به حال برایتان سؤال پیش آمده که چرا حلقههای for در زبان پایتون بسیار انعطافپذیر و زیبا هستند؟ زیرا میتوانیم توابع را در iterator حلقه for اجرا کنیم. تمامی ترفندهایی که تاکنون به آنها اشاره کردیم، چند تابع خاص را در iterator اجرا میکنند. تمامی ترفندهایی که به آنها اشاره کردیم، از این الگو پیروی میکنند:
for x in function(iterator)
ماژول itertools چند تابع متداول را پیادهسازی میکند. برای مثال، اگر یکی از توابع آن را فراموش کنیم و یا نتوانیم تابعی که بدان نیاز داریم را پیدا کنیم، خودمان میتوانیم آن تابع را بنویسیم. در ضمن، این توابع، مولد (generator) هستند و به همین دلیل به کمک آنها میتوانیم حلقههای بینهایت ایجاد کنیم.
به طور خلاصه، میتوانیم حلقه for را مطابق با خواست و نیازمان و با نوشتن یک مولد شخصیسازی شده، شخصیسازی کنیم.
به این مثال توجه کنید:
def even_only(num): for i in num: if i % 2 == 0: yield i my_list = [1, 9, 3, 4, 2, 5] for n in even_only(my_list): print(n) # 4 # 2
ما در کُد فوق مولدی موسوم به even_only تعریف کردیم. اگر از این مولد در یک حلقه for استفاده کنیم، فقط اعداد زوج در لیست تکرار میشوند.
البته مثال فوق صرفاً برای ارائه توضیحات بیشتر بود. به روشهای گوناگونی، از جمله خلاصهسازی لیست، میتوانیم این کار را انجام دهیم.
my_list = [1, 9, 3, 4, 2, 5] for n in (i for i in my_list if not i % 2): print(n) # 4 # 2
نتیجهگیری
در زبان پایتون میتوانیم حلقههای انعطافپذیر و زیبایی بنویسیم. برای انجام این کار میتوانیم از ابزاری توکار استفاده کنیم یا خودمان مولدهایی تعریف کنیم.