درس ۰۹: دستورهای کنترلی در پایتون¶
در حالت عادی جریان اجرای یک برنامه روند ثابتی دارد به این شکل که کدها سطر به سطر، از بالا به پایین خوانده و اجرا میشوند؛ دستورهای کنترلی امکانی برای کنترل یا تغییر این جریان ثابت است. با استفاده از این دستورها میتوان برای اجرای یک بلاک شرط تعیین کرد که اگر در زمان اجرا این شرط برقرار نباشد از اجرای بلاک صرف نظر خواهد شد یا میتوان شرایطی را به وجود آورد که اجرای یک بلاک را از میان چندین بلاک مشخص انتخاب گردد و همچنین میتوان اجرای یک بلاک را چندین بار تکرار کرد.
این درس به بررسی دستورهای کنترلی پایتون در دو بخش «انتخاب» و «تکرار» اختصاص یافته است. در انتها نیز بنابر ضرورت به معرفی اشیای 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
این ماژول شامل تابعهای کاربردی بسیاری است که برای مطالعه بیشتر میتوانید به صفحه آن در اسناد پایتون مراجعه نمایید.
😊 امیدوارم مفید بوده باشه