لیست مطالب
حلقه for در پایتون

حلقه for در پایتون

در برنامه‌نویسی گاهی لازم است یک مجموعه دستور را چندین‌بار اجرا کنیم. به جای نوشتن مکرر آن دستورات، از سازوکاری به نام حلقه (loop) استفاده می‌کنیم. در زبان پایتون، دو نوع حلقهٔ اصلی وجود دارد: for و while . هر یک کاربرد متفاوتی دارد؛ حلقهٔ for در پایتون زمانی به کار می‌رود که تعداد تکرار از پیش مشخص باشد یا مجموعه‌ای از آیتم‌ها برای پیمایش در اختیار داشته باشیم، در حالی که حلقهٔ while تا زمانی اجرا می‌شود که یک شرط منطقی برقرار بماند در این مقاله به‌صورت جامع به دستور حلقهٔ for در پایتون می‌پردازیم:

 

ساختار پایهٔ حلقه for

حلقهٔ for در پایتون برای پیمایش(iterate) روی مجموعه‌ای از مقادیر به کار می‌رود. به زبان ساده، این حلقه به شما اجازه می‌دهد هر یک از عناصر یک دنباله (مثل لیست، تاپل، رشته و غیره) را به ترتیب پردازش کنید. ساختار کلی آن به شکل زیر است:

				
					for variable in iterable:
        # variable انجام کاری با 

				
			

در ابتدای حلقه، متغیرِ حلقه (مثلاً variable) مقدار اولین عنصر قابل پیمایش (iterable) را می‌گیرد و بدنهٔ حلقه اجرا می‌شود. سپس به صورت خودکار حلقه به عنصر بعدی مجموعه رفته و این روند تا زمانی ادامه می‌یابد که به انتهای مجموعه برسیم. به این ترتیب، اگر دنبالهٔ ما شامل n عنصر باشد، بدنهٔ حلقه دقیقاً n بار اجرا خواهد شد

برای درک بهتر، به مثال سادهٔ زیر توجه کنید. فرض کنید یک لیست از اسامی دوستان داریم و می‌خواهیم هر اسم را چاپ کنیم:

				
					
friends = ["Ali", "Reza", "Mina"]
for name in friends:
    print("Hello", name + "!")

				
			
خروجی حاصل از اجرای این کد به صورت زیر خواهد بود:
				
					Hello Ali!
Hello Reza!
Hello Mina!

				
			

در این مثال، حلقهٔ for سه بار تکرار شده است؛ بار اول “name = “Ali، بار دوم “name = “Reza و بار سوم “name = “Mina. در هر تکرار تابع print پیام خوش‌آمدگویی را به همراه نام مربوطه چاپ می‌کند. به همین سادگی می‌توان روی عناصر یک لیست در پایتون حلقه زد و عملیات دلخواه را روی تک‌تک آنها انجام داد.

نکتهٔ تکمیلی: حلقهٔ for در پایتون بر خلاف برخی زبان‌های دیگر، به‌طور ضمنی از یک شمارندهٔ عددی استفاده نمی‌کند، بلکه مستقیماً خود عناصر را یکی‌یکی در اختیار ما قرار می‌دهد . این طراحی باعث می‌شود که در اکثر موارد نیازی به استفاده از اندیس‌های عددی یا متغیرهای کمکی برای شمارش دفعات تکرار نداشته باشیم و کد خواناتر و ساده‌تر شود

حلقه for روی ساختارهای داده پایتون

در پایتون انواع مختلفی از مجموعه‌ها (iterables) وجود دارند که می‌توان آنها را با حلقهٔ for پیمایش کرد. در این بخش به چند نوع متداول می‌پردازیم: لیست‌ها و تاپل‌ها، رشته‌ها، محدوده‌های عددی با استفاده از تابع range، و دیکشنری‌ها.

لیست‌ها و تاپل‌ها در حلقه for

لیست (list) و تاپل (tuple) هر دو از نوع دنباله (sequence) هستند و رفتاری مشابه در برابر حلقهٔ for دارند. حلقه روی لیست یا تاپل، عنصرها را به ترتیب برمی‌گرداند . تفاوت لیست و تاپل (قابل تغییر بودن لیست در مقابل ثابت بودن تاپل) تأثیری در نحوهٔ پیمایش ندارد. به مثال زیر توجه کنید که اعداد داخل یک لیست را چاپ می‌کند:

				
					numbers = [5, 8, 12]
for num in numbers:
    print(num)

				
			

خروجی

				
					
5
8
12

				
			

همان‌طور که مشاهده می‌شود، در هر تکرار متغیر num به ترتیب برابر با ۵، ۸ و ۱۲ قرار گرفته و چاپ شده است. اگر این ساختار یک تاپل بود مثلاً numbers = (5, 8, 12)، نتیجهٔ حلقه دقیقاً یکسان می‌بود.

رشته‌ها (Strings) در حلقه for

رشته‌ها در پایتون نوعی دنباله محسوب می‌شوند که از کاراکترها تشکیل شده است. بنابراین می‌توانیم با حلقهٔ for روی تک‌تک حروف یک رشته پیمایش کنیم. برای مثال:

				
					text = "Python"
for ch in text:
    print(ch)

				
			

خروجی:

				
					P
y
t
h
o
n

				
			
در اینجا متغیر ch در هر دور حلقه به ترتیب یکی از حروف رشتهٔ “Python” را می‌گیرد و چاپ می‌کند. این ویژگی برای پردازش رشته‌ها (مثلاً شمارش حروف خاص، تغییر حروف به حالت بزرگ/کوچک و …) بسیار کاربردی است.

محدوده‌های عددی با تابع range

بسیاری اوقات نیاز داریم یک حلقه را تعدادی مرتبهٔ مشخص تکرار کنیم، یا روی یک بازه‌ای از اعداد پیمایش کنیم. در پایتون برای این کار از تابع درونی ()range استفاده می‌شود دنباله‌ای از اعداد صحیح از ۰ تا n-1 تولید می‌کند. مثلاّ:

				
					
for i in range(5):
    print(i)

				
			

خروجی:

				
					0
1
2
3
4

				
			

تابع range(5) اعداد ۰ تا ۴ را به ترتیب تولید کرده و حلقه نیز آنها را چاپ کرده است. دقت کنید که عدد ۵ خود در خروجی ظاهر نشده، چرا که range(n) تا عدد ماقبل n پیش می‌رود . ما می‌توانیم range را با دو یا سه آرگومان نیز صدا بزنیم: range(start, end, step). به عنوان مثال range(1, 6) اعداد ۱ تا ۵ را تولید می‌کند و range(0, 10, 2) اعداد زوج ۰ تا ۸ (با گام ۲) را برمی‌گرداند

				
					for j in range(1, 6):
    print(j, end=" ")
print()  # برای رفتن به خط بعد
for j in range(0, 10, 3):
    print(j, end=" ")

				
			

خروجی

				
					
1 2 3 4 5 
0 3 6 9 

				
			

در مثال بالا، حلقهٔ اول با range(1, 6) اعداد ۱ تا ۵ را چاپ کرده و حلقهٔ دوم با range(0, 10, 3) اعداد ۰، ۳، ۶، ۹ را. توجه کنید که در تابع print از پارامتر ” “=end استفاده کردیم تا خروجی هر عدد در کنار عدد قبلی و با یک فاصله چاپ شود و سپس با دستور ()print یک خط جدید آغاز کردیم.

نکتهٔ کاربردی: گاهی اوقات تنها هدف ما تکرار یک عمل به تعداد دفعات مشخص است و خود مقدار متغیر حلقه اهمیتی ندارد. در چنین حالتی، طبق قرارداد رایج در پایتون از کاراکتر زیرخط (_) به عنوان متغیر حلقه استفاده می‌شود تا نشان دهد با مقدار آن کاری نداریم . مثلاّ چاپ عبارت “Hello” به‌طور ۳ بار:

				
					
for _ in range(3):
    print("Hello")

				
			

اگر این حلقه را اجرا کنید سه بار کلمهٔ “Hello” چاپ خواهد شد. استفاده از _ به خواناتر شدن کد کمک می‌کند و نشان می‌دهد که مقدار متغیر در هر تکرار مهم نیست.

دیکشنری‌ها و مجموعه‌ها در حلقه for

نوع دادهٔ دیکشنری (dict) در پایتون مجموعه‌ای از کلید و مقدار است. پیمایش یک دیکشنری با حلقهٔ for به‌صورت پیش‌فرض روی کلیدهای آن انجام می‌شود. به عبارتی دیگر، در حلقهٔ for روی یک دیکشنری، در هر تکرار یک کلید از دیکشنری در اختیار ما قرار می‌گیرد و می‌توان از آن برای دسترسی به مقدار مربوطه استفاده کرد. به عنوان نمونه:

				
					student = {"name": "Ali", "age": 21, "grade": "18"}
for key in student:
    print(key, "->", student[key])

				
			

خروجی :

				
					
name -> Ali
age -> 21
major -> 18

				
			

در مثال بالا، در هر دور حلقه متغیر key یکی از کلیدهای دیکشنری (“name”, “age”, “grade”) را می‌گیرد و ما از طریق student[key] مقدار متناظر با آن را چاپ می‌کنیم. به ترتیب خروجی توجه کنید که مطابق ترتیب درج عناصر در دیکشنری است.

در عمل، اغلب نیاز داریم هم کلید و هم مقدار را همزمان در حلقه داشته باشیم. خوشبختانه دیکشنری متدی به نام ()items دارد که مجموعه‌ای از زوج‌های (کلید، مقدار) را برمی‌گرداند. با استفاده از آن می‌توانیم در هر تکرار هر دو را دریافت کنیم :

				
					for key, value in student.items():
        print(key, ":", value)

				
			

خروجی :

				
					
name -> Ali
age -> 21
major -> 18

				
			

اکنون در هر دور حلقه، key و value به ترتیب حاوی کلید و مقدار هر ورودی دیکشنری هستند و کد ما خواناتر شده است. به طور مشابه، اگر بخواهیم فقط روی مقادیر دیکشنری حلقه بزنیم می‌توان از متد ()values و اگر فقط کلیدها مد نظر باشند از ()keys استفاده کرد (اگرچه همان حلقه روی خود دیکشنری نیز کلیدها را می‌دهد).

علاوه بر دیکشنری، نوع دادهٔ دیگری به نام مجموعه (set) نیز قابل پیمایش است. مجموعه‌ها شبیه به لیست حاوی چندین مقدار هستند با این تفاوت که ترتیب مشخصی ندارند و عناصر تکراری در آنها حذف می‌شود. پیمایش یک مجموعه با for تمام عناصر مجموعه را (به ترتیب داخلی مجموعه) در اختیار قرار می‌دهد. مثلاّ:

				
					nums = {5, 2, 9}
for x in nums:
    print(x)

				
			
خروجی (ترتیب ممکن است متفاوت باشد):
				
					2
5
9

				
			
در این مثال ساده، مجموعهٔ nums حاوی اعداد ۲، ۵ و ۹ است و حلقهٔ for آنها را (بدون ترتیب خاصی که تضمین‌شده باشد) چاپ می‌کند. در عمل، از مجموعه‌ها کمتر برای پیمایش ترتیبی استفاده می‌شود چرا که ترتیبی ندارند؛ اما اگر لازم باشد می‌توانید در صورت نیاز ابتدا آنها را به لیست مرتب‌شده تبدیل کنید و سپس پیمایش نمایید (مثلاً با استفاده از تابع sorted(nums)).

کنترل جریان در حلقه‌: break, continue و else

در هنگام استفاده از حلقه‌ها، گاهی به ساختارهایی نیاز داریم که جریان اجرای حلقه را تحت تاثیر قرار دهند؛ مثلاً بخواهیم حلقه را زودتر تمام کنیم یا بعضی تکرارهای آن را نادیده بگیریم. پایتون برای این منظور دو دستور کنترلی مهم به نام‌های break و continue را فراهم کرده است. همچنین یک ویژگی کمتر شناخته‌شده به نام else وجود دارد که می‌تواند در انتهای حلقه استفاده شود. در ادامه هر یک را بررسی می‌کنیم.

دستور break در for

دستور break باعث خروج فوری از حلقهٔ جاری می‌شود . به محض اجرای break، اجرای کل حلقه قطع شده و برنامه به اولین دستور بعد از بلوک حلقه ادامه می‌دهد. از break معمولاً زمانی استفاده می‌شود که هدف حلقه حاصل شده باشد و ادامه دادن آن لازم نباشد. به عنوان مثال، حلقهٔ زیر را در نظر بگیرید که اعداد را پیمایش می‌کند تا اولین عدد زوج را بیابد:

				
					numbers = [3, 7, 10, 15, 21]
for n in numbers:
    if n % 2 == 0:
        print("اولین عدد زوج یافت‌شده:", n)
        break

				
			

خروجی:

				
					اولین عدد زوج یافت‌شده: 10
				
			

لیست numbers را با مقدارهای ۳، ۷، ۱۰، ۱۵، ۲۱ آغاز کردیم. حلقهٔ for از ابتدا آنها را بررسی می‌کند؛ زمانی که به n = 10 می‌رسد شرط n % 2 == 0 برقرار است، لذا پیام مربوط به یافت شدن عدد زوج چاپ شده و سپس break اجرا می‌گردد. اجرای break حلقه را متوقف می‌کند، بنابراین اعداد ۱۵ و ۲۱ دیگر بررسی نمی‌شوند. پس از خاتمهٔ حلقه (به دلیل break)، ادامهٔ کد برنامه اجرا خواهد شد (در اینجا کد دیگری پس از حلقه نداریم).

دستور continue در for

دستور continue برخلاف break از حلقه خارج نمی‌شود، بلکه فقط از بدنهٔ حلقه به ابتدای دور بعدی می‌رود. به بیان دیگر، هنگامی که continue اجرا می‌شود، مابقی کدهای درون حلقه در همان تکرار نادیده گرفته شده و حلقه مستقیماً به مرحلهٔ بعد (تکرار بعدی) می‌رود. این دستور زمانی مفید است که بخواهیم برخی از تکرارهای حلقه را (بر اساس یک شرط) رد کنیم و ادامهٔ حلقه را از سر بگیریم. مثال زیر تمام اعداد ۱ تا ۱۰ را پیمایش کرده و فقط اعداد فرد را چاپ می‌کند:

				
					
for num in range(1, 11):
    if num % 2 == 0:
        continue
    print(num, "عدد فرد است")

				
			

خروجی:

				
					1 عدد فرد است
3 عدد فرد است
5 عدد فرد است
7 عدد فرد است
9 عدد فرد است

				
			

در اینجا هرگاه num زوج باشد، دستور continue اجرا می‌شود و باعث می‌گردد که دستور print برای آن تکرار خاص اجرا نشود (به جای آن، حلقه به سراغ عدد بعدی می‌رود). به همین دلیل در خروجی فقط شماره‌های فرد را مشاهده می‌کنیم. به محض برخورد حلقه به مقدار ۲، شرط برقرار شده و continue اجرا می‌شود، لذا عبارت چاپ مربوط به ۲ رد شده و حلقه به iteration بعد (۳) می‌رود و … .

بخش else در حلقه for

پایتون قابلیتی ویژه در اختیار می‌گذارد که کمتر در زبان‌های دیگر دیده می‌شود: استفاده از بخش else بعد از پایان حلقه. سینتکس کلی به صورت زیر است:

				
					
for item in iterable:
    ...break در شرایطی همراه با
else:
    ...  (اجرای این بخش اختیاری پس از اتمام حلقه)

				
			

بخش else تنها زمانی اجرا می‌شود که حلقه به طور عادی و کامل به پایان برسد؛ یعنی حلقه تمام عناصر را پیمایش کند و هیچ وقت با break خاتمه نیابد . اگر در طی اجرای حلقه دستور break اجرا شود (یا به هر روش دیگری حلقه زودتر خاتمه یابد)، بخش else رد می‌شود و اجرا نخواهد شد . این سازوکار برای سناریوهایی مفید است که در آن دنبال چیزی در میان عناصر می‌گردیم و می‌خواهیم بدانیم آیا حلقه موفق به یافتن آن شده است یا خیر. با استفاده از else می‌توانیم قطعه‌کدی بنویسیم که در صورت “پیدا نکردن مورد دلخواه در حلقه” اجرا شود.

برای روشن‌تر شدن موضوع، مثال قبل را بسط می‌دهیم تا در صورت عدم وجود عدد زوج نیز مطلع شویم:

				
					
numbers = [3, 7, 11, 15, 21]  # لیستی که این بار هیچ عدد زوج ندارد
for n in numbers:
    if n % 2 == 0:
        print("اولین عدد زوج یافت‌شده:", n)
        break
else:
    print("هیچ عدد زوجی در لیست وجود ندارد")

				
			

خروجی

				
					هیچ عدد زوجی در لیست وجود ندارد
				
			

در این مثال، حلقه تمام اعداد را بررسی می‌کند و چون هیچ کدام زوج نیستند، شرط داخل حلقه هرگز منجر به break نمی‌شود. پس از پایان طبیعی حلقه (اتمام لیست)، بخش else اجرا شده و پیام مربوطه چاپ می‌شود. در مقابل، اگر حتی یک عدد زوج وجود داشته باشد و break رخ دهد، کد داخل else اجرا نخواهد شد. به این ترتیب، می‌توانیم بدون نیاز به متغیر کمکی “یافتن” یا شرایط اضافی پس از حلقه، منطق مورد نظر را پیاده کنیم.

دستور pass در حلقه for

کلمهٔ کلیدی pass یک دستور بدون عمل است؛ یعنی هیچ کاری انجام نمی‌دهد. از pass معمولاً به عنوان فضاپرکن (placeholder) در جاهایی استفاده می‌شود که به‌طور سینتکسی نیاز به وجود یک دستور داریم اما در عمل کاری انجام نمی‌دهیم. در زمینهٔ حلقه‌ها، اگر بخواهیم بدنهٔ حلقه را عمداً خالی بگذاریم (مثلاً حلقه‌ای که فعلاً قرار است کاری انجام ندهد یا پیاده‌سازی‌اش را به بعد موکول کرده‌ایم)، می‌توانیم از pass استفاده کنیم:

				
					
for x in range(5):
    pass  # هنوز کاری برای انجام نداریم

				
			

این کد بدون خطا اجرا می‌شود و حلقه ۵ بار تکرار خواهد شد، هرچند هیچ عملی در بدنهٔ حلقه صورت نمی‌گیرد. استفاده از pass تضمین می‌کند ساختار نحو for درست و کامل باشد.

تکنیک‌ها و نکات پیشرفته در استفاده از حلقه for

پس از آشنایی با مبانی حلقهٔ for، در این بخش به چند تکنیک و نکتهٔ پیشرفته‌تر می‌پردازیم که می‌توانند کدنویسی شما را در پایتون تمیزتر، کاراتر و پایتونیک‌تر  🙂 کنند.
 

استفاده از enumerate برای حلقه‌های همراه با اندیس

در صورتی که نیاز داشته باشیم ضمن پیمایش یک دنباله، اندیس یا شمارۀ هر عنصر را نیز در اختیار داشته باشیم، یک رویکرد ممکن استفاده از تابع range همراه با تابع len است (مثلاً for i in range(len(list))). با این حال، پایتون روش زیباتر و تمیزتری به نام تابع ()enumerate فراهم کرده است که همزمان هم اندیس و هم مقدار عنصر را تولید می‌کند.

تابع enumerate(iterable) یک آبجکت قابل پیمایش برمی‌گرداند که در هر گام یک تاپل دوقسمتی (index, item) تولید می‌کند؛ که index شمارش از ۰ (به طور پیش‌فرض) و item همان عنصر مربوطه از دنباله است . به حلقهٔ زیر توجه کنید:

				
					fruits = ["apple", "banana", "cherry"]
for idx, fruit in enumerate(fruits):
    print(idx, "-", fruit)

				
			

خروجی:

				
					
0 - apple
1 - banana
2 - cherry

				
			

تابع enumerate در اینجا در هر دور حلقه یک تاپل شامل اندیس و مقدار برگردانده و ما آن را به دو متغیر idx (اندیس) و fruit (مقدار) نسبت داده‌ایم. نتیجه همان‌طور که می‌بینید چاپ اندیس‌ها (۰، ۱، ۲) به همراه نام میوه‌های متناظر است. استفاده از enumerate کد را خواناتر می‌کند و از اشتباهات احتمالی مرتبط با محاسبهٔ اندیس یا طول لیست جلوگیری می‌کند. در واقع، حتی مستندات رسمی پایتون نیز توصیه می‌کنند به جای ترکیب range و len از enumerate استفاده شود.

همچنین enumerate امکان شروع شمارش از عدد دلخواه را فراهم می‌کند. با دادن آرگومان دوم اختیاری start، می‌توانید مشخص کنید اندیس اولین عنصر چه عددی باشد. مثال:

				
					
for idx, fruit in enumerate(fruits, start=1):
    print(idx, fruit)

				
			

خروجی:

				
					1 apple
2 banana
3 cherry

				
			

در اینجا با start=1 مشخص کردیم که شمارش اندیس از ۱ آغاز شود (نه ۰). بنابراین اندیس‌های چاپ‌شده ۱ و ۲ و ۳ هستند که گاهی در کاربردهایی مانند شماره‌گذاری موارد برای کاربر مفیدتر است.

پیمایش همزمان چند مجموعه با zip

فرض کنید دو لیست مرتبط با هم داریم، مثلاّ یک لیست از نام دانش‌آموزان و یک لیست از نمرات آنها، و می‌خواهیم به طور همزمان روی هر دو لیست حلقه بزنیم؛ به طوری که در هر تکرار یک نام و نمرهٔ متناظر با هم پردازش شوند. یک روش ساده برای انجام این کار استفاده از تابع درونی ()zip است. تابع zip دو یا چند iterable را گرفته و در هر گام یک تاپل شامل عناصر متناظر از هر کدام برمی‌گرداند .

				
					names = ["Ali", "Sara", "Reza"]
scores = [85, 92, 78]
for name, score in zip(names, scores):
    print(name, "->", score)

				
			

خروجی

				
					Ali -> 85
Sara -> 92
Reza -> 78

				
			
همان‌طور که مشخص است، در هر دور حلقه، یکی از عناصر لیست names و عنصر متناظر آن از لیست scores دریافت شده و در متغیرهای name و score قرار می‌گیرند. به این ترتیب زوج “Ali و 85″، سپس “Sara و 92” و در نهایت “Reza و 78” پردازش و چاپ شده‌اند. تابع zip کار ما را بسیار ساده می‌کند؛ در غیاب آن شاید مجبور بودیم با اندیس‌ها و حلقه‌های تو در تو چنین چیزی را پیاده‌سازی کنیم که خطای بیشتری به همراه داشت. نکته: اگر طول دو لیست (یا به طور کلی مجموعه‌هایی که به zip می‌دهیم) برابر نباشد، حلقه تا جایی پیش می‌رود که یکی از مجموعه‌ها تمام شود و عناصر مازاد مجموعهٔ بلندتر نادیده گرفته می‌شوند. بنابراین بهتر است مطمئن شویم طول مجموعه‌های ورودی یکسان است یا در صورت نیاز از ابزارهایی مثل itertools.zip_longest برای تکمیل خودکار مقادیر خالی استفاده کنیم.

حلقه‌های تو در تو (Nested Loops) در پایتون

 

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

				
					matrix = [
    [1, 2, 3],
    [4, 5, 6]
]
for row in matrix:
    for elem in row:
        print(elem, end=" ")
    print()  # پس از اتمام هر سطر، رفتن به خط جدید

				
			

خروجی:

				
					
1 2 3 
4 5 6 

				
			

در اینجا حلقهٔ بیرونی لیست‌های داخلی (row) را یک‌به‌یک برمی‌دارد و حلقهٔ داخلی روی عناصر هر لیست (elem) پیمایش می‌کند. نتیجه چاپ تمام اعداد ماتریس است، با این قالب که هر سطر ماتریس در یک خط مجزا نمایش داده شده است. از حلقه‌های تو در تو می‌توان برای الگوریتم‌های گوناگون (مثلاً جستجوی تمامی ترکیب‌های ممکن دو لیست، و …) استفاده کرد. باید در نظر داشت که افزایش عمق حلقه‌های تو در تو می‌تواند به افزایش نمایی تعداد تکرارها منجر شود و بر کارایی اثر بگذارد، بنابراین معمولاً عمق بیش از ۲ یا ۳ توصیه نمی‌شود مگر در موارد ضروری.

پرهیز از تغییر ساختار در حال پیمایش حلقه for

یکی از مشکلات رایجی که ممکن است به ویژه برای مبتدیان پیش بیاید، تغییر دادن یک مجموعه در حین پیمایش حلقه است. مثلاّ حذف کردن یا اضافه کردن عنصر به یک لیست در داخل حلقه‌ای که در حال پیمایش همان لیست است. انجام چنین کاری در پایتون می‌تواند به رفتارهای پیش‌بینی‌نشده منجر شود؛ چون حلقهٔ for از روی یک iterable  که در حال تغییر است. در بهترین حالت، برخی عناصر ممکن است دوبار پیمایش شوند یا برخی اصلاً پیمایش نشوند، و در مورد دیکشنری‌ها حتی منجر به خطای زمان اجرا (run time error) می‌شود (پایتون اجازه نمی‌دهد اندازهٔ دیکشنری در حین iteration تغییر کند و خطای runtime می‌دهد .)

به عنوان نمونه، کد زیر را در نظر بگیرید که تلاش می‌کند از یک لیست عددی همه اعداد زوج را حذف کند:

				
					numbers = [1, 2, 3, 4, 5, 6]
for n in numbers:
    if n % 2 == 0:
        numbers.remove(n)
print(numbers)

				
			

این کد به درستی کار نخواهد کرد، زیرا با حذف هر عنصر از لیست، ترتیب و ایندکس‌بندی لیست تغییر می‌کند و حلقه ممکن است برخی اعداد را نادیده بگیرد. (مثلاّ در اینجا خروجی ممکن است [1, 3, 5] شود که به نظر درست می‌آید، اما بسته به وضعیت داخلی لیست حین حذف، می‌توانست برخی اعداد زوج را حذف نکند یا رفتارهای عجیب نشان دهد).

برای جلوگیری از این مشکلات، دو راه کلی وجود دارد : یا بر روی کپی‌ای از مجموعهٔ اصلی حلقه بزنیم و تغییرات را روی نسخهٔ اصلی اعمال کنیم، یا اینکه مجموعهٔ نتیجهٔ نهایی را در یک ساختار جدید بسازیم. راه حل اول را می‌توان با ساختن یک کپی سطحی از لیست (مثلاً با [:]numbers یا تابع ()list روی آن) انجام داد. راه حل دوم نیز بهره‌گیری از یک لیست کمکی یا استفاده از قابلیت‌های تولید لیست (list comprehension) است که در ادامه خواهیم دید.

مثلاً برای حل مثال فوق به شکل صحیح، می‌توانیم چنین بنویسیم:

				
					numbers = [1, 2, 3, 4, 5, 6]
for n in numbers[:]:  # پیمایش روی کپی لیست
    if n % 2 == 0:
        numbers.remove(n)
print(numbers)

				
			

خروجی:

				
					[1, 3, 5]
				
			

در اینجا با [:]numbers یک کپی از لیست اصلی ایجاد کرده‌ایم و حلقه را روی آن کپی اجرا کرده‌ایم. در نتیجه، حذف عناصر از لیست اصلی در طی حلقه، اختلالی در ترتیب iteration ایجاد نمی‌کند زیرا حلقه در واقع روی نسخهٔ کپی در حال حرکت است (و نسخهٔ کپی تغییری نمی‌کند). حاصل کار لیست اصلی بدون اعداد زوج است.

همین قاعده در مورد دیکشنری (و سایر ساختارهای تغییرپذیر) نیز صادق است؛ اگر بخواهیم مواردی را حین پیمایش حذف یا اضافه کنیم، بهتر است ابتدا کلیدهایی که مدنظر برای حذف هستند را در لیستی جمع‌آوری کرده و پس از حلقه، حذف را انجام دهیم، یا از روش مشابه بالا استفاده کنیم (مثلاً پیمایش روی ()dict.copy یا روی ()dict.items که در لیستی کپی‌شده).

استفاده از List Comprehension به جای حلقه در ساخت لیست‌ها

یکی از امکانات قدرتمند و پایتونیک برای کار با مجموعه‌ها، ویژگی تولید لیست به روش فشرده یا به اصطلاح List Comprehension است. این سینتکس پایتون به ما اجازه می‌دهد در قالبی کوتاه و خوانا، یک لیست جدید را بر اساس عملیات روی اعضای یک لیست (یا iterable) موجود ایجاد کنیم . در حقیقت، list comprehension در بسیاری موارد می‌تواند جایگزین حلقه‌های for سنتی شود که هدفشان ساختن یک لیست جدید است.

شکل کلی یک list comprehension به صورت زیر است:

				
					new_list = [expression for item in iterable (if condition)]

				
			

قسمت شرطی اختیاری است. برای مقایسه، دو روش ساخت لیستی از مربع اعداد ۰ تا ۹ را ببینید؛ یکی با استفاده از حلقهٔ for معمولی و دیگری با list comprehension:

				
					squares = []
for x in range(10):
    squares.append(x**2)
print(squares)

				
			

و روش فشرده:

				
					
squares = [x**2 for x in range(10)]
print(squares)

				
			

در هر دو حالت خروجی یکسان است:

				
					[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
				
			

روش دوم بسیار کوتاه‌تر و بهینه‌تر است؛ کل حلقه و عمل افزودن به لیست را در یک خط خلاصه کرده‌ایم . در مثال بالا، هر دو عدد ۰ تا ۹ را به توان ۲ رسانده و در لیست squares قرار می‌دهد. استفاده از list comprehension زمانی توصیه می‌شود که منطق ایجاد لیست نسبتاً ساده باشد و وجود آن در یک خط به خوانایی صدمه نزند. در واقع، list comprehension‌ها برای ساخت لیست جدید کاربرد دارند و خودشان جایگزین حلقهٔ for در سایر موارد (مثل پیمایش و انجام عمل در هر دور بدون تولید لیست) نیستند.

همچنین می‌توان در list comprehension شرط هم قرار داد؛ مثلاً اگر بخواهیم فقط اعداد زوج را به توان ۲ در لیست بگذاریم:

				
					even_squares = [x2 for x in range(10) if x % 2 == 0]
print(even_squares)

				
			

خروجی

				
					[0, 4, 16, 36, 64]
				
			

این عبارت تنها مجذور اعداد زوج (۰، ۲، ۴، ۶، ۸) را در لیست قرار داده است. ترکیب این روش‌های فشرده با قابلیت‌های درونی (built-in) پایتون، در عین اینکه کد را مختصر می‌کند می‌تواند کارایی را هم افزایش دهد، زیرا پیاده‌سازی این ساختارها در خود زبان بهینه است . البته همواره باید تعادل را رعایت کرد؛ اگر منطق تولید لیست بسیار پیچیده باشد، شاید یک حلقهٔ معمولی خواناتر و قابل‌درک‌تر باشد.

منابع استفاده شده: w3schoolswiki python realpython
نوشته های مرتبط

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *