درس ۰۹: دستورهای کنترلی در پایتون¶
Photo by Lynda Sanchez¶
در حالت عادی جریان اجرای یک برنامه روند ثابتی دارد به این شکل که کدها سطر به سطر، از بالا به پایین خوانده و اجرا میشوند؛ دستورهای کنترلی امکانی برای کنترل یا تغییر این جریان ثابت است. با استفاده از این دستورها میتوان برای اجرای یک بلاک شرط تعیین کرد که اگر در زمان اجرا این شرط برقرار نباشد از اجرای بلاک صرف نظر خواهد شد یا میتوان شرایطی را به وجود آورد که اجرای یک بلاک را از میان چندین بلاک مشخص انتخاب گردد و همچنین میتوان اجرای یک بلاک را چندین بار تکرار کرد.
این درس به بررسی دستورهای کنترلی پایتون در دو بخش «انتخاب» و «تکرار» اختصاص یافته است. در انتها نیز بنابر ضرورت به معرفی اشیای iterator در پایتون پرداخته شده است.
✔ سطح: مقدماتی
انتخاب¶
با استفاده از دستور انتخاب میتوان بر حسب شرایط برنامه در زمان اجرا تعیین کرد که آیا یک بلاک دستور اجرا شود یا خیر و همچنین از بین دو یا چند بلاک دستور کدام یک انتخاب و اجرا گردد. رایجترین دستور انتخاب در پایتون if است که میتواند به سه شکل «تک انتخابی»، «دو انتخابی» و «چند انتخابی» پیادهسازی گردد. این ساختار در ادامه بررسی خواهد شد.
دستور if¶
۱. ساختار ساده (تک انتخابی)
این ساختار یک دستور مرکب است که در سرآیند آن یک «شرط» (Condition) بررسی میگردد و تنها در صورتی که این شرط برقرار باشد بدنه اجرا خواهد گشت؛ در غیر این صورت مفسر از اجرای دستور(های) بدنه صرف نظر کرده و به سراغ نخستین دستور بعد از این ساختار میرود. این ساختار با استفاده از کلمه کلیدی if و الگویی مشابه پایین پیادهسازی میگردد:
if condition :
StatementBlock
منظور از شرط عبارتی است که میتوان آن را به یکی از مقدارهای بولی (True یا False) ارزیابی نمود؛ در اینجا اگر شرط برابر True ارزیابی گردد بخش بدنه دستور if اجرا میگردد. به نمونه کدهای پایین توجه نمایید:
>>> a = 5
>>> b = 3
>>> if a > b:
... print("a is greater than b")
...
a is greater than b
>>>
>>> if a == b:
... print("a is equal to b")
...
>>>
در نمونه کد بالا شرط برابر False ارزیابی شده و از اجرای بدنه خودداری شده است؛ بنابراین هیچ متنی در خروجی چاپ نشده است.
>>> if a > b and a >= 0:
... print("a is positive and greater than b")
...
a is positive and greater than b
>>>
همانطور که در نمونه کد بالا نیز مشاهده میشود میتوان از عملگرهای منطقی (not ،or ،and) برای بررسی برقرار بودن (یا نبودن) همزمان چندین شرط بهره گرفت.
میدانیم که: عدد یک و تمام اعداد مخالف صفر در پایتون برابر مقدار بولی True و عدد صفر، اشیا خالی به مانند "" یا [] برابر مقدار False ارزیابی میشوند:
>>> if 1:
... print("Condition is True")
...
Condition is True
>>>
>>> if []:
... print("Condition is True")
...
>>>
>>> a = False
>>> if not a:
... print("Condition is True")
...
Condition is True
>>>
میتوان از ساختار if به شکل تودرتو (Nested) نیز بهره برد. در این حالت بدنه دستور if حاوی یک یا چند دستور if دیگر میشود که البته آنها نیز میتوانند حاوی دستورهای if دیگری در بدنه خود باشند:
>>> d = {'name': 'Jhon', 'job': 'programmer', 'age': 40}
>>> if d['age'] >= 40:
... if d['job'] == 'programmer':
... print(d['name'])
...
Jhon
>>>
به مثال دیگری با استفاده از walrus operator (عملگر شیرماهی - درس ششم) و f-string (درس هفتم) توجه نمایید:
>>> # Python >= 3.8
>>> a_list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> if (n := len(a_list)) > 5:
... print(f"List is too long ({n} elements, expected <= 5)")
...
List is too long (10 elements, expected <= 5)
۲. ساختار همراه با else (دو انتخابی)
با استفاده از کلمه کلیدی else میتوان بلاکی را برای اجرا در حالتی که شرط برقرار نیست - زمانی که شرط if برابر مقدار بولی False ارزیابی میگردد - تعیین کرد. else یک بخش جدا است که سرآیند و بدنه مخصوص به خود را دارد؛ این سرآیند میبایست فاقد هر گونه شرطی باشد:
>>> a = False
>>> if a:
... print("Condition is True")
... else:
... print("Condition is False")
...
Condition is False
>>>
>>> a = 7
>>> if a in [1, 2, 3]:
... print("a is in list")
... else:
... print("a is not in list")
...
a is not in list
>>>
>>> d = {'name': 'Bob', 'job': 'designer', 'age': 45}
>>> if d['age'] >= 40:
... if d['job'] == 'programmer':
... print(d['name'])
... else:
... print(d['name'], d['job']) # Will be executed
... else:
... if d['age'] >= 35:
... print(d['name'], 'Between 35 and 40 years old')
... else:
... print(d['name'], 'Less than 35 years old')
...
Bob designer
>>>
۳. ساختار همراه با elif (چند انتخابی)
دستور if را میتوان گسترش داد و بخشهای بیشتری را با شرطهای گوناگون ایجاد نمود؛ به این صورت که ابتدا شرط بخش if بررسی میگردد و چنانچه برابر True ارزیابی نگردد، شرط مربوط به نختسین بخش elif بررسی میگردد که اگر باز هم برابر True نشود شرط بخش elif بعدی بررسی خواهد شد و به همین صورت ادامه مییابد؛ در انتها نیز اگر هیچ کدام از شرطها (if و elif) برابر True نشوند آنگاه بدنه مربوط به بخش else (در صورت وجود) اجرا میگردد. الگوی این ساختار مانند پایین است:
if condition_1:
statements
elif condition_2:
statements
elif condition_3:
statements
else:
statements
هر
elifیک بخش جدا است که سرآیند و بدنه مخصوص به خود را دارد.تعداد بخشهای
elifاختیاری است و محدودیتی در آن وجود ندارد.بخش
elifنمیتواند قبل ازifیا بعد ازelseقرار بگیرد.در این ساختار نیز وجود بخش
elseاختیاری است.
در این ساختار بخشها به ترتیب از بالا به پایین بررسی میشوند و با True ارزیابی شدن شرط هر بخش، بدنه مربوط به آن اجرا و از بررسی دیگر بخشها صرف نظر میگردد. به نمونه کد پایین توجه نمایید:
>>> percent = 60
>>> if percent == 100:
... print('100 %')
... elif percent >= 75:
... print('75-100 %')
... elif percent >= 50:
... print('50-75 %')
... elif percent >= 25:
... print('25-50 %')
... else:
... print('less than 25 %')
...
50-75 %
>>>
اگر بخواهیم نمونه کد بالا را با استفاده از if های تودرتو پیادهسازی نماییم به شکل پایین خواهد شد:
>>> percent = 60
>>> if percent == 100:
... print('100 %')
... else:
... if percent >= 75:
... print('75-100 %')
... else:
... if percent >= 50:
... print('50-75 %')
... else:
... if percent >= 25:
... print('25-50 %')
... else:
... print('less than 25 %')
...
50-75 %
>>>
چنانچه قصد دارید تمام شرطهای مورد نظر بررسی شوند میتوانید از چند دستور if به شکل متوالی استفاده نمایید:
1# File: Documents/script.py
2# Python 3.x
3
4import sys
5
6# Get script argument and convert it to an integer
7percent = int(sys.argv[1])
8
9if percent == 100:
10 print('100 %')
11if percent >= 75:
12 print('75-100 %')
13if percent >= 50:
14 print('50-75 %')
15if percent >= 25:
16 print('25-50 %')
17if percent < 25:
18 print('less than 25 %')
user> cd Documents/
user> python script.py 60
50-75 %
25-50 %
دستور match/case¶
در صورتی که سابقه برنامهنویسی با زبانهای دیگری همچون C و Java را داشته باشید حتما با دستور switch نیز آشنا هستید؛ تا پیش از نسخه 3.10 پایتون این دستور در زبان پایتون پیادهسازی نشده بود و تنها میتوانستیم از ساختار if/elif/else استفاده نماییم. ولی اکنون پایتون از ساختار مشابهی به نام match/case پشتیبانی میکند که ساختاری برابر زیر دارد (برای مطالعه بیشتر: [PEP 634] و [PEP 635] و [PEP 636]):
match value:
case matching_rule_1: statement_1
case matching_rule_2: statement_2
case matching_rule_3: statement_3
.
.
.
این دستور یک مقدار را دریافت میکند و با الگوهای درج شده توسط case مطابقت میدهد (از بالا به پایین) و با نخستین عمل انطباق موفق، دستورهای مرتبط با آن را اجرا کرده و سپس پایان مییابد.
خواهید دید که این دستور بسیار منعطف بوده و پیشرفتهتر از دستور switch سنتی است. با یک مثال ساده شروع میکنیم:
>>> list = [4, 5, 6, 0, 2, 1, 3]
>>>
>>> first_num = list[0]
>>>
>>> match first_num:
... case 0: print('Zero')
... case 1: print('One')
... case 2: print('Two')
... case 3: print('Three')
... case 4: print('Four')
... case 5: print('Five')
... case 6: print('Six')
...
Four
>>>
در مثال بالا مقدار حروفی مربوط به عدد اندیس صفر از شی list چاپ خواهد شد.
مثالی دیگر، تشخیص زوج بودن یک عدد:
>>> list = [4, 5, 6, 0, 2, 1, 3]
>>>
>>> first_num = list[0]
>>>
>>> match first_num % 2:
... case 0:
... print('The number is even')
... case 1:
... print('The number is odd')
...
The number is even
هدف اصلی از ایجاد دستور match/case در پایتون، سادهسازی و افزایش خوانایی کد در زمان استفاده از دستور if/elif/else است.
هر بخش case میتواند بیش از یک انطباق را بررسی کند. برای این منظور میتوان از کاراکتر | برای جداسازی الگوها استفاده نمود:
>>> list = [4, 5, 6, 0, 2, 1, 3]
>>>
>>> first_num = list[0]
>>>
>>> match first_num:
... case 0 | 2 | 4 | 6 | 8:
... print('The number is even')
... case 1 | 3 | 5 | 7 | 9:
... print('The number is odd')
...
The number is even
همچنین میتوان یک case پیشفرض نیز برای این ساختار در نظر گرفت، برای پردازش مقدار در زمانی که با هیچ یک از الگوهای موجود تطابق پیدا نکرد. برای درج case پیشفرض از الگو _ استفاده میگردد. این الگو در ساختار match/case با هر مقداری تظابق داده میشود و میبایست برای جلوگیری از خطاهای منطقی، حتما به عنوان آخرین case قرار داده شود:
>>> list = [1, 2, 3, 'A', 'A', 'AAA']
>>> first_num = list[-1]
>>> match first_num:
... case 0 | 2 | 4 | 6 | 8:
... print('The number is even')
... case 1 | 3 | 5 | 7 | 9:
... print('The number is odd')
... case _:
... print("The received value is not a number")
...
The received value is not a number
در بخش case حتی میتوان از دستور if نیز استفاده نمود:
>>> list = [4, -5, 6, 0, 2, -1, 3]
>>>
>>> num = list[0]
>>>
>>> match num:
... case num if num < 0:
... print('The number is negative')
... case num if num == 0:
... print('The number is zero')
... case num if num > 0:
... print('The number is positive')
...
The number is positive
به این نگارش یا سینتکس از دستور if در جامعه پایتون، تکنیک guard گفته میشود. در این ساختار متغیری که در پشت if قرار میگیرد، همان مقدار دریافتی است. این متغیر میتواند هر نامی داشته باشد ولی حتما میبایست با متغیر درون دستور if همنام باشد (در مثال بالا برای جلوگیری از ابهام، همنام با خود مقدار دریافتی در نظر گرفته شده است). در این شرایط چنانچه ارزیابی دستور if برابر مقدار True باشد، دستورهای case آن اجرا میگردد و در غیر اینصورت الگوی case بعدی مورد پردازش قرار خواهد گرفت.
تکرار¶
گاهی نیاز پیدا میکنیم که بلاکی را چندین بار پشت سرهم اجرا نماییم. به ساختار تکرار، «حلقه» (Loop) گفته میشود؛ در ادامه به بررسی ساختار دو حلقه ارایه شده در زبان پایتون خواهیم پرداخت.
دستور while¶
این دستور مرکب یک حلقه تکرار است که یک شرط را در سرآیند خود بررسی میکند و چنانچه شرط برابر مقدار True ارزیابی شود، دستورهای بدنه را اجرا میکند؛ مفسر پس از اتمام اجرای بدنه دوباره به سرآیند برگشته و شرط را بررسی میکند که اگر شرط هنوز هم برقرار باشد از نو دستورهای بدنه اجرا میگردند. در حالت عادی روند تکرار اجرای بدنه تا زمانی که شرط سرآیند برابر True ارزیابی گردد ادامه خواهد یافت. الگوی این دستور به مانند پایین است:
while condition :
statements
شرط همواره میبایست از درون بدنه کنترل شود به گونهای که در مرحله خاصی برابر مقدار False ارزیابی گردد؛ در غیر این صورت یک حلقه بینهایت ایجاد میشود که مفسر هیچگاه نمیتواند از اجرای آن خارج شود. برای نمونه اجرای دستور پایین هیچگاه توسط مفسر پایتون پایان نمیپذیرد و برای اتمام آن میبایست از سیستم عامل کمک گرفت:
>>> while 1:
... print('Press Ctrl+C to stop!')
...
Press Ctrl+C to stop!
Press Ctrl+C to stop!
Press Ctrl+C to stop!
[..]
ولی در نمونه کد پایین مقدار متغیر a از درون بدنه کنترل و در هر بار اجرا یک واحد کاهش مییابد؛ بنابراین اجرای حلقه تنها تا زمانی که شرط نقض نشده باشد ادامه مییابد:
>>> a = 5
>>> while a > 0:
... print(a)
... a -= 1 # a = a - 1
...
5
4
3
2
1
>>>
در نمونه کد بالا بهتر میبود به جای عبارت a > 0 تنها از خود متغیر a به عنوان شرط حلقه استفاده نماییم؛ چرا که در هر مرتبه اجرا یک واحد از آن کم میشود و با رسیدن به مقدار صفر به صورت خودکار توسط مفسر پایتون به مقدار False ارزیابی و تکرار اجرای بدنه حلقه متوقف میگردد.
به عنوان نمونهای دیگر، فاکتوریل (Factorial) عدد ۱۰ را میتوان به صورت پایین محاسبه کرد:
>>> a = 10
>>> n = 1
>>> while a >= 1:
... n = n * a
... a -= 1
...
>>> print(n)
3628800
دستور continue
این دستور در هر نقطه از بخش بدنه که آورده شود، دستورهای بعد از آن نادیده گرفته میشوند و جریان اجرا به ابتدای حلقه یعنی بخش سرآیند پرش میکند. برای نمونه میخواهیم اعداد صحیح زوجی که کوچکتر از ۱۰ هستند را بر روی خروجی نمایش دهیم. در نمونه کد پایین برای اعداد فرد دستور continue از ادامه اجرا و نمایش آنها جلوگیری میکند و جریان اجرا را به ابتدای حلقه پرش میدهد:
>>> n = 10
>>> while n:
... n -= 1
... if n % 2 != 0:
... continue
... print(n)
...
8
6
4
2
0
>>>
البته مثال بالا را بدون continue نیز میتوان به انجام رساند:
>>> n = 10
>>> while n:
... n -= 1
... if n % 2 == 0:
... print(n)
دستور break
این دستور در هر نقطه از بخش بدنه که آورده شود، دستورهای بعد از آن نادیده گرفته میشوند و جریان اجرا از حلقه خارج میشود. در نمونه کد پایین با هر اجرای بدنه یک واحد به counter افزوده میشود و هرگاه مقدار آن برابر ۴ گردد، بدون توجه به شرط، اجرای حلقه متوقف میشود:
>>> counter = 0
>>> while counter < 100:
... if counter == 4:
... break
... print(counter)
... counter += 1
...
0
1
2
3
>>>
در while نیز میشود از بخش else استفاده نماییم؛ به این صورت که اگر حلقه به صورت طبیعی پایان پذیرد - و نه توسط دستور break - آنگاه بدنه else اجرا میگردد.
نمونه کد پایین بررسی میکند که آیا عدد n یک «عدد اول» (Prime number) هست یا خیر؛ این اعداد بزرگتر از یک بوده و به جز خود و عدد یک بر هیچ عدد دیگری بخش پذیر نیستند. بنابراین اگر عددی کوچکتر از n (به جز یک) پیدا شود که بر آن بخشپذیر باشد (یعنی باقی مانده تقسیم بر آن صفر باشد) اول نبودن عدد n ثابت میشود و حلقه به کمک دستور break متوقف میگردد:
>>> n = 23
>>> i = 2
>>> while i < n:
... if n % i == 0:
... print(n, "is not a prime number")
... break
... i += 1
... else:
... print(n, "is a prime number")
...
23 is a prime number
>>>
دستور for¶
این دستور مرکب یک حلقه تکرار است که بر اساس تعداد عضوهای یک شی دنباله یا در حالت کلیتر یک شی تکرارکننده (iterator) - که در انتها بررسی خواهد شد - اجرای دستورهای بدنه را تکرار میکند. الگوی این دستور به شکل پایین است:
for target in object:
statements
هر حلقه for دقیقا به تعداد عضوهای شی object تکرار میگردد؛ هر بار یک عضو از دنباله (یا تکرارکننده) object با حفظ ترتیب اعضا به متغیر target انتساب داده میشود و یک مرتبه بدنه اجرا میگردد؛ این روند تا پایان پیمایش عضوهای object ادامه مییابد. از متغیر target میتوان در داخل بدنه استفاده کرد که در مرتبه نخست اجرای حلقه به عضو یکم و با اجراهای بعدی به عضوهای بعدی از object اشاره خواهد داشت. به نمونه کدهای پایین توجه نمایید:
>>> for item in [1, 2, 3]:
... print(item)
...
1
2
3
>>>
>>> for char in 'python':
... print(char)
...
p
y
t
h
o
n
>>>
>>> L = [(1, 2), (3,4), (5, 6)]
>>> for a, b in L:
... print(a, b)
...
1 2
3 4
5 6
>>>
در نمونه کد بالا، از آنجا که هر عضو دنباله خود یک دنباله دو عضوی است، بنابراین از دو متغیر برای اشاره به شی پیمایش استفاده شده است.
>>> L = [(1, 2), (3,4), (5, 6)]
>>> for both in L:
... a, b = both
... print(a, b)
...
1 2
3 4
5 6
>>>
در نمونه کد بالا، متغیر both در هر مرتبه تکرار به یک شی توپِل اشاره دارد.
>>> a, *b, c = (1, 2, 3, 4)
>>> a, b, c
(1, [2, 3], 4)
>>> for a, *b, c in [(1, 2, 3, 4), (5, 6, 7, 8)]:
... print(a, b, c)
...
1 [2, 3] 4
5 [6, 7] 8
>>>
>>> d = {'name': 'Jhon', 'job': 'designer', 'age': 40}
>>> for key in d:
... print(key)
...
name
job
age
>>>
در حالت عادی برای یک شی دیکشنری، کلیدهای آن پیمایش میشوند.
>>> d = {'name': 'Jhon', 'job': 'designer', 'age': 40}
>>> d.items()
dict_items([('name', 'Jhon'), ('job', 'designer'), ('age', 40)])
>>> for key, value in d.items():
... print(key, value)
...
name Jhon
job designer
age 40
>>>
توجه
معمولا از حلقه for در مواقعی که تعداد تکرار مشخص باشد و از حلقه while زمانی که تعداد تکرار نامشخص است استفاده میشود.
مانند حلقه while در اینجا نیز میتوان از دستورهای continue و break استفاده کرد. همچنین حلقه for میتواند شامل بخش else باشد.
مثال تشخیص عدد اول در حلقه while را با استفاده از حلقه for بازنویسی میکنیم:
>>> n = 23
>>> for i in range(2, n):
... if n % i == 0:
... print(n, "is not a prime number")
... break
... else:
... print(n, "is a prime number")
...
23 is a prime number
>>>
تابع (range(stop:
این تابع [اسناد پایتون] یک شی از نوع range را برمیگرداند؛ این شی یک دنباله تغییر ناپذیر است که معمولا از آن برای پیمایش در حلقه for استفاده میشود. با تبدیل شی range به نوع لیست خواهیم دید که این شی یک دنباله مرتب از اعداد صفر تا آرگومان stop (و نه خود آن) است؛ آرگومان stop میبایست یک عدد صحیح مثبت باشد:
>>> r = range(10)
>>> type(r)
<class 'range'>
>>> r
range(10)
>>> print(r)
range(10)
>>> list(r)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> tuple(r)
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
>>> import sys
>>> sys.getsizeof(r)
48
این تابع را میتوان به صورت دو آرگومانی ((range(start, stop) نیز فراخوانی نمود که آرگومان یکم عدد آغازین دنباله را تعیین میکند و میتواند یک عدد منفی نیز باشد:
>>> list(range(2, 10))
[2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(-2, 10))
[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
در این حالت میتوان از آرگومان سومی نیز برای تعیین گام یا فاصله بین اعداد بهره گرفت:
>>> list(range(2, 10, 2))
[2, 4, 6, 8]
>>> list(range(2, 10, 3))
[2, 5, 8]
>>> list(range(2, 10, 4))
[2, 6]
هر سه آرگومان میبایست از نوع صحیح باشند.
برای تعیین آرگومان stop منفی، میبایست آرگومان گام را نیز به شکل منفی تعیین نمود:
>>> list(range(2, -10, -1)) [2, 1, 0, -1, -2, -3, -4, -5, -6, -7, -8, -9] >>> list(range(2, -10, -2)) [2, 0, -2, -4, -6, -8]
>>> list(range(-2, -10, -1)) [-2, -3, -4, -5, -6, -7, -8, -9] >>> list(range(-2, -10, -2)) [-2, -4, -6, -8]
چند مثال ساده دیگر:
>>> L = ['a', 'b', 'c', 'd']
>>> for i in range(len(L)):
... print(L[i])
...
a
b
c
d
>>>
>>> s = 'pythonprogramminglanguage'
>>> for c in s[9:13]:
... print(c)
...
g
r
a
m
>>>
>>> reven = range(0, 10, 2)
>>> list(reven)
[0, 2, 4, 6, 8]
>>> rodd = range(1, 10, 2)
>>> list(rodd)
[1, 3, 5, 7, 9]
>>> list(zip(reven, rodd))
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
>>> L = []
>>> for a, b in zip(reven, rodd):
... L.append(a*b)
...
>>> L
[0, 6, 20, 42, 72]
میتوان نتایج حلقه for را مستقیم در یک شی لیست قرار داد؛ برای نمونه دستور پایین را در نظر بگیرید:
>>> L = []
>>> for x in range(5):
... L.append(x**2)
...
>>> L
[0, 1, 4, 9, 16]
که میتوان خیلی ساده آن را به صورت پایین بازنویسی کرد:
>>> [x ** 2 for x in range(5)]
[0, 1, 4, 9, 16]
این عمل، List Comprehensions خوانده میشود که توسط درس سیزدهم شرح داده خواهد شد.
و به عنوان مثالهایی دیگر به نمونه کدهای پایین توجه نمایید:
>>> y = 7
>>> [y * x for x in range(10)]
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63]
>>> L = [(1, 2), (3, 4), (5, 6)]
>>> [a + b for a, b in L]
[3, 7, 11]
>>> [a * b for a, b in zip(range(0, 10, 2), range(1, 10, 2))]
[0, 6, 20, 42, 72]
>>> [(a, b) for a, b in zip(range(0, 10, 2), range(1, 10, 2))]
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
از دستورهای مرکب پایتون میتوان در داخل بدنه یکدیگر بهره برد که البته این موضوع برای دستورهای for و while نیز صادق است. از هر دو این دستورها میتوان بر حسب نیاز در داخل بدنه یکدیگر یا به شکل تودرتو استفاده کرد:
>>> for i in range(1, 5):
... for j in range(0, i):
... print(i)
...
1
2
2
3
3
3
4
4
4
4
>>>
به نمونه کد بالا توجه نمایید؛ با هر بار تکرار حلقه یکم تمام دستورهای بدنه آن که شامل یک حلقه دیگر است اجرا میگردد. از متغیر i درون حلقه داخلی نیز استفاده شده است. در بار نخستِ اجرای حلقه بیرونی مقدار i برابر عدد 1 قرار داده میشود که در این صورت اجرای حلقه داخلی تنها یک بار تکرار میگردد 1 == ((len(range(0, 1 و یک مقدار 1 در خروجی نمایش داده میشود، بار دوم i برابر عدد 2 میشود و در نتیجه حلقه داخلی دو بار تکرار میگردد که بر اثر آن دو مقدار 2 در خروجی چاپ میگردد. این روند تا پایان تکرار حلقه بیرونی ادامه مییابد.
تابع print به صورت پیشفرض پس از اجرا و چاپ مقدار به سطر بعدی میرود. [در درس بعد چگونگی تغییر این رفتار بررسی خواهد شد]
اگر از پیش با زبانهایی نظیر C یا Java آشنایی دارید؛ برای درک بهتر ساختار حلقه for پایتون نمونه کد پایین که به زبان Java است را در نظر بگیرید:
int[][] array = { { 1, 2 }, { 3 }, { 4, 5, 6 } };
for ( int row = 0; row < array.length; row++ )
{
for ( int column = 0; column < array[ row ].length; column++ )
System.out.printf( "%d ", array[ row ][ column ] );
System.out.println();
}
// Paul Deitel, Harvey Deitel "Java: How to Program" (9th Edition) page 270
1 2
3
4 5 6
که میتوانیم آن را توسط زبان پایتون به شکل پایین پیادهسازی نماییم:
>>> array = ((1, 2), (3,), (4, 5, 6))
>>> for row in range(0, len(array)):
... for column in range(0, len(array[row])):
... print("%d " % array[row][column])
... print()
تابع (enumerate(iterable:
علاوه بر تابع ()range در حلقههای for میتوانیم از تابع ()enumerate [اسناد پایتون] نیز استفاده کنیم. این تابع یک شی دنباله یا تکرارکننده را به عنوان آرگومان دریافت میکند و یک شی از نوع enumerate برمیگرداند:
>>> L = ['a', 'b', 'c']
>>> e = enumerate(L)
>>> type(e)
<class 'enumerate'>
>>> e
<enumerate object at 0x7fc76a6b92d0>
>>> print(e)
<enumerate object at 0x7fc76a6b92d0>
>>> import sys
>>> sys.getsizeof(e)
72
با تبدیل این شی به یک شی لیست مشاهده میشود که این شی عضوهای آرگومان ورودی خود را به شکل جفتهایی به همراه اندیس موقعیت آنها ذخیره کرده است (index, value):
>>> list(e)
[(0, 'a'), (1, 'b'), (2, 'c')]
استفاده از این تابع در مواقعی که پیمایش یک دنباله غیر عددی یا بررسی اندیس دنباله حلقه را در نظر داشته باشید بسیار مفید است:
>>> s = 'python'
>>> for index, value in enumerate(s):
... print('%s) %s' % (index, value * 7))
...
0) ppppppp
1) yyyyyyy
2) ttttttt
3) hhhhhhh
4) ooooooo
5) nnnnnnn
>>>
>>> s = 'python'
>>> [v * i for i, v in enumerate(s)]
['', 'y', 'tt', 'hhh', 'oooo', 'nnnnn']
این تابع همچنین یک آرگومان اختیاری با نام start دارد که با مقدار دادن به آن میتوان عدد ابتدایی شمارش اندیسها را تعیین نمود؛ مقدار پیشفرض این آرگومان عدد صفر است:
>>> seasons = ['Spring', 'Summer', 'Fall', 'Winter']
>>> list(enumerate(seasons))
[(0, 'Spring'), (1, 'Summer'), (2, 'Fall'), (3, 'Winter')]
>>> list(enumerate(seasons, start=1))
[(1, 'Spring'), (2, 'Summer'), (3, 'Fall'), (4, 'Winter')]
شی تکرارکننده (iterator)¶
در این بخش قصد داریم با مفهوم iterator (تکرارکننده) در پایتون آشنا شویم. برای این منظور بهتر است ابتدا مفهوم iterable (تکرارپذیر) را بدانیم.
تمام انواع دنباله یک iterable هستند؛ در واقع به اشیایی با این قابلیت که بتوان در هر لحظه یک عضو درون آن را دستیابی نمود iterable گفته میشود. اکثر انواع آماده شی که در پایتون میشناسیم یک iterable است؛ انواع شی رشته، لیست، توپِل، دیکشنری، range ،zip یا یک شی فایل (file) و هر شی از کلاسی که خودتان به همراه متد ویژه ()__iter__ تعریف نمایید یک iterable هستند [ویکیپایتون].
در آینده پس از مطالعه دروس مربوط به شی گرایی (هفدهم تا بیست و دوم) قادر به ساخت کلاس و استفاده از متدهای ویژه در پایتون خواهید بود. در آن زمان می توانید خود با پیادهسازی متد ویژه ()__next__ یک شی iterator ایجاد نمایید. اما در این مرحله ما فرآیند ایجاد را با یک ماژول از کتابخانه استاندارد پایتون پیش خواهیم برد.
میتوان یک شی iterator را تنها با استفاده از تابع آماده ()iter [اسناد پایتون] ایجاد کرد. این تابع یک شی iterable را به عنوان آرگومان دریافت میکند و یک شی iterator از آن بر میگرداند:
>>> L = [1, 2, 3, 4, 5]
>>> type(L)
<class 'list'>
>>> itr = iter(L)
>>> type(itr)
<class 'list_iterator'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>
>>> itr = iter(t)
>>> type(itr)
<class 'tuple_iterator'>
>>> s = 'python'
>>> type(s)
<class 'str'>
>>> itr = iter(s)
>>> type(itr)
<class 'str_iterator'>
>>> d = {'name': 'Bob', 'age': 40}
>>> type(d)
<class 'dict'>
>>> itr = iter(d)
>>> type(itr)
<class 'dict_keyiterator'>
یک شی iterator این قابلیت را دارد که میتوان عضوهای درون آن را یکی یکی با استفاده از متد ()__next__ پیمایش کرد؛ این متد در بار نخستِ فراخوانی عضو یکم شی و در دفعات بعدی فراخوانی به ترتیب عضوهای بعدی را برمیگرداند:
>>> L = [1, 2, 3, 4, 5]
>>> itr = iter(L)
>>> itr.__next__()
1
>>> itr.__next__()
2
>>> itr.__next__()
3
با فراخوانی پی در پی این متد و رسیدن به انتهای پیمایش؛ زمانی که دیگر عضوی برای برگرداندن وجود ندارد یک خطا - البته درست این است که بگوییم یک استثنا (Exception) - با نام StopIteration گزارش میگردد:
>>> itr.__next__()
4
>>> itr.__next__()
5
>>> itr.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
این دقیقا همان کاری است که در دستور for به انجام میرسد. زمانی که از یک دنباله برای پیمایش در این دستور استفاده میکنید؛ for در پشت صحنه آن را به یک iterator تبدیل و سپس پیمایش یک به یک عضوها را آغاز میکند. در هر لحظه که StopIteration رخ دهد، متوجه پایان دنباله شده و تکرار حلقه را پایان میبخشد.
با استفاده از ماژول itertools میتوانید iterator های بینهایت (Infinite) یا بدون توقف ایجاد نمایید. برای نمونه تابع cycle درون این ماژول، شی iterator ای میسازد که در انتهای پیمایش متوقف نمیشود و از نو به ابتدای شی برگشته و عضو یکم را برمیگرداند:
>>> import itertools
>>> L = [1, 2, 3, 4, 5]
>>> itr = itertools.cycle(L)
>>> type(itr)
<class 'itertools.cycle'>
>>> itr.__next__()
1
>>> itr.__next__()
2
>>> itr.__next__()
3
>>> itr.__next__()
4
>>> itr.__next__()
5
>>> itr.__next__()
1
>>> itr.__next__()
2
این ماژول شامل تابعهای کاربردی بسیاری است که برای مطالعه بیشتر میتوانید به صفحه آن در اسناد پایتون مراجعه نمایید.
😊 امیدوارم مفید بوده باشه