مبانی ثانویه‌ی پایتون - منطق شرطی if و elif + آشنایی با حلقه‌ها

Professional Python: if and for

12 اسفند 1399
درسنامه درس 8 از سری پایتون حرفه‌ای
Python حرفه ای: مبانی ثانویه - شرط if (قسمت 08)

به فصل جدید سری آموزش پایتون حرفه ای خوش آمدید. در فصل قبل با مبانی اولیه آشنا شدیم و حالا نوبت به مبانی ثانویه است. مباحثی که در این فصل بررسی خواهیم کرد شامل شرط ها، حلقه ها، توابع و غیره است و یکی از پیش نیازهای فصل های بعدی خواهد بود بنابراین نباید از این فصل رد شوید.

ما تا این قسمت کدی را می نوشتیم و سپس مفسر پایتون آن را برایمان اجرا می کرد. روند اجرای این کدها همیشه خطی بود، یعنی از خط اول شروع کرده و سپس به خط دوم و سوم و چهارم و غیره می رفتیم اما قدرت اصلی توسعه و برنامه نویسی در این است که ما می توانیم روند اجرای یک کد را متوقف کنیم یا اینکه قسمتی از کد را به صورت مشروط اجرا کنیم یا قسمتی از کد را هزاران بار اجرا کنیم!

منطق شرطی در if و elif

ما در فصل قبل با boolean ها در پایتون آشنا شدیم و می دانیم که آن ها یا false و یا true هستند و مقدار دیگری نمی گیرند. منطق شرطی یا conditional logic با اینکه محدود به boolean ها نیست اما بر پایه همین بحث استوار شده است. فرض کنید ما یک خودروی هوشمند را داشته باشیم که فقط به افراد خاصی اجازه روشن کردن موتور را بدهد:

  • افرادی که به سن قانونی رسیده اند.
  • و افرادی که گواهینامه دارند.

بر این اساس می توانیم دو متغیر داشته باشیم که نماینده همین موضوع باشند:

is_old = True
has_license = True

if is_old:
  print('You are old enough to drive')

من دو متغیر is_old (به سن قانونی رسیده است؟) و has_license (گواهینامه دارد؟) را تعریف کرده ام و فعلا هر دو را روی true گذاشته ام. تا این قسمت چیز جدیدی نداریم اما در ادامه از دستور if (در فارسی به معنی «اگر») استفاده کرده ام که یک شرط خاص را گرفته و بررسی می کند؛ اگر این شرط برابر true باشد کدهای بعدی اجرا می شوند اما اگر شرط برابر false باشد کدهای بعدی اجرا نخواهند شد. برای استفاده از این دستور چند نکته مهم وجود دارد: اولا پس از نوشتن if و پاس دادن مقدار مورد نظرتان (is_old) باید از علامت دو نقطه استفاده کنید. دوما اگر پس از تایپ علامت دو نقطه کلید enter را فشار داده تا به خط بعد بروید، متوجه می شوید که خط بعدی با فاصله شروع می شود. این مسئله در کد بالا نیز مشخص است و قبل از print یک فضای خالی (دو عدد اسپیس) داریم. کدهایی که پس از دستور if در پایتون می آیند و این فضای خالی را دارند از نظر پایتون متعلق به if هستند و فقط زمانی اجرا می شوند که شرط if صحیح باشد. در صورتی که کدها بدون این فضای خالی (به آن indentation می گوییم) باشند، دیگر به شرط if وابسته نخواهند بود. به شرط if و کدهای وابسته به آن یک بلوک کد یا code block می گوییم. با اجرای کد بالا طبیعتا رشته You are old enough to drive چاپ می شود چرا که is_old صحیح بوده و شرط if صحیح می شود.

ما می توانیم ارتباط indentation یا «فرورفتگی» با دستور if در پایتون را به سادگی نشان بدهیم. به کد زیر توجه کنید:

is_old = False
has_license = True

if is_old:
  print('You are old enough to drive')

print('This is not inside the if statement')

من در ابتدا is_old را False کرده ام، سپس یک دستور print دیگر را اضافه کرده ام که indentation یا فرورفتگی ندارد (یعنی قبل از آن فضای خالی نداریم) و رشته This is not inside the if statement را چاپ می کند که به فارسی یعنی «این کد داخل شرط if قرار ندارد». با اجرای این کد نتیجه زیر را می گیرید:

This is not inside the if statement

چرا دستور print اول (رشته You are old enough to drive) برایمان چاپ نشده است؟ به دلیل اینکه is_old برابر با false است و همانطور که گفتم کدهای درون if فقط زمانی اجرا می شوند که داده پاس داده شده به آن برابر با true باشد. این در حالی است که دستور print دوم دارای فرورفتگی نبوده و بنابراین جزئی از شرط if نیست و طبیعتا فارغ از نتیجه شرط if اجرا می شود. در ضمن یادتان باشد که شما می توانید بی نهایت کد را درون این شرط if بنویسید و کد بالا فقط یک مثال است.

منظور من از کنترل روند اجرای کد دقیقا همین است؛ ما با اضافه کردن یک شرط if می توانیم باعث اجرا نشدن یک یا چند خط کد بشویم. در مرحله بعدی باید این شرط را کمی پیشرفته تر کنیم. چطور؟ ما می توانیم به شرط if یک قسمت دوم به نام else (در فارسی به معنی «در غیر این صورت») اضافه کنیم تا حالت غیر از شرط را مدیریت کنیم. به مثال زیر توجه کنید:

is_old = False
has_license = True

if is_old:
  print('You are old enough to drive')
else:
  print('This is the else block')

کد بالا می گوید اگر is_old صحیح است رشته You are old enough to drive چاپ شود،‌ «در غیر این صورت» رشته This is the else block چاپ شود. در غیر این صورت یعنی حالت برعکس شرط اصلی یا به زبان ساده تر هنگامی که شرط اصلی برقرار نباشد (is_old برابر False باشد) وارد قسمت else می شویم و کدهای درون آن اجرا خواهند شد.

 شاید بگویید در این کد و کد قبلی دستور print دوم اجرا شد بنابراین تفاوت در چیست؟ برای استفاده از این دستور باید به جنبه منطقی آن توجه کنید: قسمت else فقط زمانی اجرا می شود که if غلط باشد و همچنین در نهایت تنها دو خروجی ممکن خواهیم داشت: یا if اجرا می شود و یا else اجرا خواهد شد. در هیچ حالتی ممکن نیست که هم if و هم else اجرا شوند چرا که یکی برعکس دیگری است. به کد زیر توجه کنید:

is_old = False
has_license = True

if is_old:
  print('You are old enough to drive')
else:
  print('This is the else block')

print('outside of the if statement')

در حال حاضر اگر کد بالا را اجرا کنیم نتیجه زیر را می گیریم:

This is the else block
outside of the if statement

آیا متوجه تفاوت شدید؟ else فقط زمانی اجرا می شود که if برقرار نباشد اما کدهای خارج از دستور if و else بدون در نظر گرفتن آن ها اجرا خواهند شد و برایشان مهم نیست که if صحیح باشد یا غلط باشد. سوال بعدی ما اینجاست که اگر بیشتر از یک شرط داشتیم چطور؟ تا این قسمت ما از متغیر has_license استفاده نکرده ایم. چطور می توانیم این موضوع را نیز بررسی کنیم؟

is_old = False
has_license = True

if is_old:
  print('You are old enough to drive')
elif has_license:
  print('You have a license')
else:
  print('This is the else block')

print('outside of the if statement')

دستور elif مخفف else if به شما اجازه می دهد یک شرط دیگر را نیز بررسی کنید. کد بالا می گوید اگر is_old صحیح بود رشته You are old enough to drive و اگر has_license صحیح بود رشته You have a license و در غیر این صورت (در صورتی که هر دو شرط بالا غلط بودند) رشته This is the else block چاپ شود. نهایتا رشته outside of the if statement نیز فارغ از تمامی حالت های بالا چاپ خواهد شد. با این حساب با اجرای کد بالا نتیجه زیر را دریافت می کنید:

You have a license
outside of the if statement

توجه داشته باشید که در شرط if در کد بالا تنها یکی از سه حالت زیر رخ می دهد:

  • یا is_old صحیح است بنابراین دستور print اول چاپ می شود.
  • یا has_license صحیح است بنابراین دستور print دوم چاپ می شود.
  • یا هیچ کدام از دو شرط بالا صحیح نیستند بنابراین دستور print سوم چاپ می شود.

شاید شما بگویید اگر is_old و has_license هر دو True باشند چطور؟

is_old = True
has_license = True

if is_old:
  print('You are old enough to drive')
elif has_license:
  print('You have a license')
else:
  print('This is the else block')

print('outside of the if statement')

نتیجه اجرای این کد:

You are old enough to drive
outside of the if statement

آیا متوجه شدید؟ در حالتی که هر دو شرط صحیح باشند از نظر پایتون is_old صحیح است و وارد شرط آن می شویم چرا که اولین شرط است. در واقع زمانی که ما از if و elif استفاده می کنیم تنها یکی از این دو (اولین شرط) اجرا می شود بنابراین از این نظر می توانیم قسمت شرطی کد خود را به دو دسته تقسیم کنیم:

  • شرط ها (if یا elif)
  • بلوک else

بلوک else فقط زمانی اجرا می شود که شرط های قبل همگی غلط باشند. همچنین باید بدانید که اجازه دارید به هر تعدادی که خواستید elif داشته باشید:

is_old = True
has_license = True

if is_old:
  print('You are old enough to drive')
elif has_license:
  print('You have a license')
elif other_variable:
  print('Other variable')
else:
  print('This is the else block')

print('outside of the if statement')

اشکالی در منطق برنامه

با توجه به توضیحاتی که برایتان دادم احتمالا متوجه شده اید که منطق کد ما مشکل دارد. اگر فقط یکی از شرط های if و elif اجرا می شوند یعنی صدور اجازه رانندگی ناقص است. چرا؟ یک فرد برای اینکه اجازه رانندگی داشته باشد باید دو شرط را داشته باشد: اولا به سن قانونی رسیده باشد و دوما دارای گواهینامه باشد. این در حالی است که برنامه ما فقط یکی از این دو شرط را بررسی می کند؛ یعنی به سن قانونی رسیده باشد یا گواهینامه داشته باشد. چطور می توانیم این مشکل را حل کنیم؟

مسئله اینجاست که مقدار پاس داده شده به شرط if یک expression است و اگر یادتان باشد expression ها یک مقدار جدید را تولید می کردند. این بدان معنا است که لازم نیست داده پاس داده شده به if حتما True یا False باشد بلکه باید نهایتا معادل true یا false قرار بگیرد. با این حساب:

is_old = True
has_license = True

if is_old and has_license:
  print('You are old enough to drive')
else:
  print('This is the else block')

کلیدواژه and به دستور if در پایتون می گوید برای صحیح بودن شرط باید هر دو مقدار پاس داده شده صحیح باشند. نتیجه اجرای کد بالا You are old enough to drive است اما اگر یکی از دو متغیر را false کنیم رشته This is the else block نمایش داده می شود.

فرورفتگی در زبان پایتون

فرورفتگی یا indentation به همان فضای خالی (چند کاراکتر اسپیس) قبل از کدها گفته می شود. در بسیاری از زبان های برنامه نویسی، این فضای خالی برای زیبایی و مرتب کردن کدها است اما از نظر فنی کاربردی ندارد. این مسئله در زبان پایتون استثناء است و فضای خالی قبل از کدها معنی خاصی برای مفسر پایتون دارد به همین دلیل این قسمت اختصاصی را برای آن در نظر گرفتم تا حتما بدانید که قرار دادن این فضای خالی مهم است. شما می توانید این فضای خالی را به روش های مختلفی ایجاد کنید؛ به طور مثال استفاده از کلید tab یا استفاده از کلید space. همچنین معمولا به صورت قرارداد می گویند که بهتر است برای ایجاد فرورفتگی از ۴ کاراکتر اسپیس استفاده کنید اما این موارد سلیقه ای است و در صورتی که از آن ها پیروی نکنید مشکلی پیش نمی آید. تنها مسئله مهم این است که به اشتباه فرورفتگی کدهای خود را پاک نکنید.

مقادیر Truthy و Falsey

ما می دانیم که دو مقدار True و False از مقادیر boolean هستند اما مقادیر دیگری به نام truthy و falsey داریم که به معنی شبه صحیح و شبه غلط هستند. به کد زیر توجه کنید:

is_old = 'Hello'
has_license = 5

if is_old and has_license:
  print('You are old enough to drive')
else:
  print('This is the else block')

من در کد بالا مقادیری تصادفی را به دو متغیر خودمان داده ام. حالا به نظر شما چه اتفاقی می افتد؟ با اجرای کد بالا نتیجه زیر را می گیریم:

You are old enough to drive

چطور؟ اگر یادتان باشد چندین جلسه قبل لیستی از مقادیر false در زبان پایتون به شما ارائه داده بودم:

  • List های خالی
  • Tuple های خالی
  • dictionary های خالی
  • set های خالی
  • string (رشته) های خالی
  • range های خالی
  • عدد صفر از نوع عدد صحیح (0)
  • عدد صفر از نوع عدد اعشاری (0.0)
  • عدد complex صفر (0j)
  • none
  • خود false

غیر از مواردی که در لیست بالا می بینید بقیه موارد در منطق کامپیوتری پایتون برابر True یا صحیح هستند. با این حساب رشته های غیر خالی نیز truthy یا شبه صحیح هستند بنابراین در یک شرط صحیح حساب می شوند. چطور؟ در پس زمینه، پایتون این مقادیر را به boolean تبدیل می کند (دقیقا مانند زمانی که ()bool را صدا می زدیم). این مسئله برای اعداد غیر از صفر نیز صحیح است بنابراین هر دو متغیر ما شبه صحیح بوده و بلوک if اجرا می شود.

اپراتور Ternary

اپراتور ternary یک اپراتور شرطی دیگر در بسیاری از زبان های برنامه نویسی است و پایتون هم از آن استفاده می کند. در واقع اپراتور ternary دقیقا مانند if و elif است با این تفاوت که یک روش میانبر است تا کدهایمان خوانا تر و تمیز تر باشند بنابراین نمی توان در همه جا از آن استفاده کرد. ساختار استفاده از این اپراتور به شکل زیر است:

condition_if_true if condition else condition_if_false

این ساختار شاید در نظر اول عجیب به نظر برسد بنابراین بهتر است یک مثال از آن برایتان بزنم. فرض کنید بخواهیم مقدار یک متغیر را بر اساس یک شرط تعیین کنیم. من این کار را در مثال زیر انجام داده ام:

is_friend = True

can_message = "Message Allowed" if is_friend else "Message not Allowed"

print(can_message)

ما در کد بالا یک متغیر به نام is_friend داریم که true است، سپس یک متغیر به نام can_message را داریم و می خواهیم مقدار آن را بر اساس یک شرط (مقدار متغیر is_friend) محاسبه کنیم. اگر به کد بالا نگاه کنید می بینید که گفته ایم مقدار متغیر can_message در صورتی که is_friend برابر true باشد برابر با رشته Message Allowed خواهد بود و در غیر این صورت برابر با Message not Allowed خواهد بود. در صورتی که کد بالا را اجرا کنید نتیجه Message Allowed را دریافت می کنید اما اگر متغیر is_friend را false کنید نتیجه Message not Allowed را دریافت خواهید کرد.

مفهوم short circuiting

در بخش های قبلی با کلیدواژه and آشنا شدیم:

is_old = True
has_license = True

if is_old and has_license:
  print('You are old enough to drive')
else:
  print('This is the else block')

به شما گفته بودم که در این حالت فقط زمانی وارد قسمت if می شوید که هم is_old و هم has_license صحیح باشد. مسئله اینجاست که اگر وضعیت طور دیگری بود و ما تنها یکی از این دو شرط را می خواستیم چطور؟ آیا روش خاصی برای انجام این کار وجود دارد؟ خوشبختانه کلیدواژه دیگری به نام or داریم که این کار را برایمان انجام می دهد. تفاوت میان and (به معنی «و») و or (به معنی «یا») دقیقا مانند تفاوت آن ها در زبان انسان ها است! بگذارید برایتان مثالی بزنم:

  • زمانی که می گوییم افراد باید دارای گواهینامه باشند و به سن قانونی رسیده باشند یعنی باید هر دو شرط (سن و گواهینامه) برقرار باشد.
  • زمانی که می گوییم افراد باید دارای گواهینامه باشند یا به سن قانونی رسیده باشند یعنی باید یکی از این دو شرط (سن و گواهینامه) برقرار باشد.

به نظر شما کدام یک از این دو کلیدواژه (and و or) سریع تر و بهینه تر هستند؟ اینجاست که بحث short circuiting یا «اتصال کوتاه» مطرح می شود. به کد زیر توجه کنید:

if is_old or has_license:

زمانی که مفسر پایتون به is_old می رسد مقدارش را می گیرد (مثلا True) و سپس به کلیدواژه or می رسد. به محض اینکه به or برسیم «اتصال کوتاه» رخ می دهد. یعنی چه؟ یعنی مفسر پایتون می بیند که صحیح بودن یکی از دو شرط کافی است (به خاطر or) بنابراین به محض اینکه is_old را ببیند وارد if می شود و بقیه موارد را بررسی نمی کند. با این حساب مفسر پایتون دیگر نیازی به بررسی متغیر has_license ندارد و مستقیما وارد if می شویم.

اتصال کوتاه در استفاده از and نیز رخ می دهد. به طور مثال کد زیر را در نظر بگیرید:

if is_old and has_license:

در صورتی که is_old غلط (false) باشد مشخص است که شرط باطل است چرا که برای برقراری and هر دو شرط باید صحیح باشند و با غلط بودن یکی کل شرط غلط می شود. با این حساب چه نیازی برای محاسبه مقدار  has_license است؟ در چنین حالتی نیز short circuiting یا اتصال کوتاه رخ داده و دیگر has_license را محاسبه نمی کنیم.

اپراتورهای منطقی

اپراتورهای منطقی، همانطور که از نامشان پیداست، مسئولیت ایجاد ارتباط منطقی بین داده ها را دارند. ما تا به حال با دو اپراتور منطقی به نام های and و or آشنا شده ایم و می دانیم کارشان چیست اما آیا اپراتورهای منطقی دیگری نیز وجود دارد؟ بله! اولین اپراتور منطقی در این بخش اپراتورهای کمتری و بیشتری (علامت < و >) هستند که دو مقدار را با هم مقایسه می کنند. به طور مثال:

print(4 > 5)
print(4 < 5)

با اجرای کد بالا نتیجه زیر را می گیرید:

False
True

چرا؟ به دلیل اینکه 4 بزرگتر از 5 نیست (5 < 4) اما 5 بزرگتر از 4 است (5 > 4). اپراتور منطقی ما، اپراتور برابری است. اگر یادتان باشد برایتان توضیح داده بودم که علامت = در برنامه نویسی به معنی برابری نیست بلکه اپراتور انتساب (assignment) است. با این حساب اپراتور برابری چیست؟

print(5 == 5)

دو علامت مساوی پشت سر هم به معنی برابر بودن داده ها هستند. آیا این شرط صحیح است؟ ما می دانیم که ۵ برابر با ۵ است بنابراین صحیح است و نتیجه True برایمان چاپ می شود. اگر این کار را با اپراتور انتساب انجام می دادیم (5 = 5) به خطا برخورد می کردیم چرا که مفسر پایتون تصور می مکند ما در حال انتساب عدد 5 به متغیری به نام 5 هستیم و همانطور که گفتم نام متغیر ها نباید با عدد شروع شود.

البته در نظر داشته باشید که می توانید از چندین اپراتور منطقی نیز استفاده کنید. مثال:

print(1 < 2 < 3 < 4 < 5)

از آنجایی که ۱ کمتر از ۲ و ۲ کمتر از ۳ و ۳ کمتر از ۴ و ۴ کمتر از ۵ می باشد عبارت بالا معادل True بوده و همین مقدار نیز برایمان چاپ می شود. همچنین می توانیم دو اپراتور را ترکیب کنیم. به طور مثال با ترکیب > و = می توان چنین کدی را نوشت:

print(1 >= 0)

این کد می گوید آیا عدد صفر بزرگتر یا مساوی با ۱ است؟ توجه کنید که «بزرگتر یا مساوی» کلمه «یا» را دارد بنابراین صحیح بودن یکی از این دو حالت true خواهد بود. طبیعتا پاسخ سوال بالا مثبت است و عدد یک بزرگتر از صفر است بنابراین true را دریافت می کنیم. به مثال زیر نیز توجه کنید:

print(1 >= 1)

آیا یک بزرگتر یا مساوی با یک است؟ بله عدد یک مساوی با یک است بنابراین باز هم true دریافت می کنیم. اپراتور بعدی ما =! است که به معنی «مساوی نبودن» می باشد. زمانی که می خواهیم مساوی بودن را بررسی کنیم از == استفاده می کردیم اما در موضوعات منطقی علامت تعجب به معنی «نفی» می باشد بنابراین جای اولین علامت تساوی را گرفته و شرط را منفی می کند. مثال:

print(1 != 3)

این کد می گوید آیا عدد ۱ با عدد ۳ برابر نیست؟ پاسخ مثبت است؛ اعداد ۱ و ۳ با هم مساوی نیستند بنابراین true برگردانده می شود. آخرین اپراتور این بخش نیز not نام دارد. این اپراتور یک مقدار را گرفته و نتیجه منطقی و برعکس شده آن را برمی گرداند. به طور مثال:

print(not(True))

مقدار برعکس شده True برابر با false است بنابراین با اجرای کد بالا نتیجه false را می گیریم.  البته شما می توانید پرانتزهای آن را نیز قرار ندهید:

print(not True)

با اجرای این کد باز هم همان نتیجه false را دریافت می کنید. از آنجایی که این روش تمیز تر و ساده تر است من از آن استفاده می کنم اما در صورتی که دوست داشتید پرانتزهای not را بگذارید هیچ مشکلی نیست. من توضیح دادم که not نتیجه منطقی و برعکس شده را برمی گرداند؛ ما متوجه بخش «برعکس شده» شدیم اما «نتیجه منطقی» یعنی چه؟ برای درک این مطلب باید به مثال زیر نگاهی بیندازید:

print(not 1 == 1)

کد بالا ابتدا ۱ == ۱ را بررسی می کند و نتیجه را به صورت boolean برمی گرداند که در اینجا true است، سپس این نتیجه به not داده می شود و not هم آن را برعکس می کند. در واقع هر مقداری که به not بدهید، آن را به صورت منطقی (در قالب boolean) تبدیل کرده و سپس آن را برعکس می کند. به مثال زیر توجه کنید:

print(not 'Roxo')

در این مثال رشته ساده Roxo را به not داده ام بنابراین not ابتدا این رشته را به مقدار boolean تبدیل می کند (رشته هایی که خالی نباشند true هستند) و سپس آن را برعکس می کند بنابراین نتیجه false را دریافت می کنیم.

تفاوت is و ==

قبل از اینکه بخواهیم از بحث اپراتورهای منطقی خارج شویم باید با نکته ای بسیار مهم اما مخفی آشنا شویم. به نظر شما با اجرای کدهای زیر چه نتیجه ای را می گیریم:

print(True == 1)
print('' == 1)
print([] == 1)
print(10.0 == 10)
print([] == [])

در اینجا پنج خط کد داریم و شما باید حدس بزنید مقدار خروجی آن ها چه مقداری خواهد بود. نتیجه به شکل زیر خواهد بود:

True
False
False
True
True

آیا تعجب کردید؟ بیایید در ابتدا کد اول را بررسی کنیم. شاید بگویید مگر True با 1 برابر است؟ آیا علامت == برای بیان تساوی نبود؟ حرف شما درست است اما در زبان پایتون مانند بسیاری از زبان های برنامه نویسی دیگر زمانی که از == استفاده می کنیم منظورمان «تساوی بدون احتساب نوع داده» است. یعن چه؟ یعنی زمانی که از اپراتور == استفاده می کنید باید همه چیز به boolean تبدیل شود بنابراین مثال اول ما در واقع بدین شکل است:

print(True == bool(1))

عدد ۱ برابر True است و بدین صورت نتیجه نیز True می باشد. اگر این مسئله را برای مثال های دیگر نیز در نظر بگیرید نتیجه را درک می کنید. در مثال دوم رشته خالی برابر false است و عدد ۱ برابر true است بنابراین با هم مساوی نبوده و نتیجه مقایسه ما false خواهد بود. قبلا این موارد را برایتان توضیح داده بودم. مسئله زمانی جالب می شود که به کد زیر برسیم:

print('1' == 1)

شاید با خودتان بگویید که پاتون حتما رشته ۱ و عدد ۱ را برابر true گرفته و سپس true برایمان چاپ می شود اما با اجرای کد بالا عبارت false را می گیرید! کم کم به چنین رفتارهای عجیب و غریبی عادت خواهید کرد. مسئله اینجاست که پایتون در این کد رشته 1 را به boolean تبدیل نمی کند! بنابراین باید خودمان این کار را انجام بدهیم:

print(bool('1') == 1)

با این کار مقدار true را دریافت خواهید کرد. شما کم کم چنین مسائلی را در پایتون درک خواهید کرد اما نگران چنین کدهایی نباشید. ما هنگامی که می خواهیم دو مقدار را مقایسه کنیم یا آن دو مقدار را به صورت دستی به boolean تبدیل می کنیم و یا اینکه مطمئن می شویم که data type هر دو یکی باشد.

سوالی که در اینجا مطرح می شود این است که اگر بخواهیم مقایسه ما شامل نوع داده یا همان data type نیز بشود چطور؟ در چنین حالتی باید از اپراتور is استفاده کنیم.

print(True is 1)
print('' is 1)
print([] is 1)
print(10.0 is 10)
print([] is [])

با اجرای این کد نتیجه زیر را می گیریم:

False
False
False
False
False

چرا اینطور شد؟ به دلیل اینکه is علاوه بر مقدار، نوع داده را نیز بررسی می کند یا به عبارتی یک مقایسه مطلق داریم و طبیعتا تمام مقایسه های بالا false می شوند. به طور مثال در خط اول یک boolean (مقدار True) را با یک عدد صحیح (مقدار 1) مقایسه کرده ایم. نه تنها مقدارشان متفاوت است بلکه نوع داده هایشان نیز متفاوت است (یکی عدد و دیگری boolean می باشد). یا در خط سوم یک لیست خالی را با یک عدد مقایسه کرده ایم بنابراین حتما مقدار false را می گیریم.

البته نکته بسیار مهم تر در مورد is اینجاست که is علاوه مقدار و نوع داده، محل داده در مموری را نیز بررسی می کند! یعنی چه؟ به مثال زیر توجه کنید:

print(True is True)
print(1 is 1)
print([] is [])

به نظر شما نتیجه اجرای کد بالا به چه صورت خواهد بود؟

True
True
False

آیا تعجب کردید؟ دو خط اول که نباید محل بحث باشد چرا که True علاوه بر نوع مقدار یکسانی با True دارد و 1 و 1 نیز با هم از هر دو جهت برابر هستند اما آیا یک لیست خالی برابر یک لیست خالی نیست؟ هر زمان که لیستی را تعریف می کنید یک لیست جدید در مموری ساخته می شود و همانطور که گفتم is محل داده ها در مموری را نیز بررسی می کند بنابراین این دو لیست کاملا متفاوت بوده و با هم یکی نیستند. به همین دلیل است که خط آخر به ما false می دهد.

با این حساب چرا برای دو عدد 1 و 1 مقدار true را گرفته ایم؟ به دلیل اینکه این نوع داده (و اعداد صحیح یا boolean ها) آنقدر ساده است که در مموری در یک محل قرار می گیرد اما این مسئله برای داده ساختار هایی مثل dictionary یا list یا tuple صحیح نیست. حالا اگر به جای is از اپراتور مقایسه برای تساوی (==) استفاده کنم چطور؟

print([] == [])

نتیجه true خواهد بود! چرا که ما در این حالت محل آن ها در مموری را در نظر نمی گیریم.

مسائلی مانند مسائلی که در این بخش توضیح دادم از مباحث ریز پایتون هستند که ممکن است شما را به اشتباه بیندازند اما جای نگرانی نیست. یادگیری این مسائل به زمان نیاز دارد بنابراین با کسب تجربه این مسائل برایتان ساده خواهند شد.

آشنایی با حلقه ها

در این بخش با یکی از مهم ترین مباحث برنامه نویسی به نام loops یا حلقه ها آشنا می شویم. ما در بخش های قبلی و به کمک شرط ها و اپراتورهای منطقی یاد گرفتیم که چطور روند اجرایی برنامه را کنترل کنیم تا همیشه از خط اول تا آخر برنامه اجرا نشود. loop ها شاید به نوعی حالت معکوس تلقی شوند چرا که به ما اجازه می دهند قسمتی از کدها را چندین و چند بار اجرا کنیم (هزاران بار یا میلیون ها بار یا ...). ما در زبان پایتون دو نوع حلقه متفاوت داریم و با اینکه کلیت هر دو یکی است (یک بلوک کد را چندین بار اجرا می کنند) اما در جزئیاتی متفاوت هستند.

حلقه های For

اولین حلقه ای که با آن آشنا می شویم، حلقه for است. به شبه کد زیر توجه کنید:

for item in iterable:
	do something

بگذارید برایتان توضیح بدهم. در ابتدا دستور for را می نویسیم و سپس نام یک متغیر را به آن می دهم که من item را داده ام اما شما می توانید هر نام دیگری را به آن بدهید. item در واقع در هر گردش از حلقه برابر یکی از اعضای iterable ما است (در ادامه متوجه این حرف من خواهید شد). در مرحله بعدی کلیدواژه in و در نهایت یک iterable یا «گردش پذیر» را پاس می دهیم.  iterable یعنی داده ای که بتوانیم روی آن با حلقه ها گردش کنیم. در جلسه بعد مفصلا درباره iterable ها صحبت می کنیم اما فعلا بدانید که رشته ها و لیست ها دو مثال ساده از iterable ها هستند، یعنی می توانیم با حلقه ها روی آن ها گردش کنیم. در نهایت قسمت do something (به معنی «کاری انجام بده» - کد نیست) را داریم که باید به جای آن کدهای خودتان را قرار بدهید.

بهترین راه برای روشن شدن این موضوع یک مثال ساده است:

for item in 'roxo':
    print(item)

من در این مثال از رشته ساده roxo استفاده کرده ام بنابراین می خواهیم با حلقه for روی این رشته گردش کنیم. باز هم می گویم که item فقط نامی برای تک تک اعضای درون iterable (در اینجا تک تک کاراکتر های رشته roxo) است و شما می توانید هر نام دیگری مانند letter را برایش انتخاب کنید. از آنجایی که for مانند شرط if دارای بلوک کدهای خودش می باشد از علامت دو نقطه استفاده کرده و حتما قبل از دستور print فضایی خالی قرار می دهیم. به نظر شما با اجرای کد بالا چه نتیجه ای را می گیریم؟

r
o
x
o

آیا متوجه شدید؟ حلقه for در ابتدا رشته roxo (یک iterable) را گرفته و سپس از ایندکس صفر آن شروع می کند. ایندکس صفر یعنی عضو اول یا همان حرف r بنابراین item (یا هر نامی که برای این متغیر نوشته باشید) در شروع برابر با r خواهد بود. حالا ما وارد بلوک کدهای for شده و دستور print را اجرا می کنیم. تا اینجای کار در گردش اول از حلقه بوده ایم. تفاوت حلقه ها در اینجاست که با تمام شدن دستور print از حلقه for خارج نمی شویم بلکه به ابتدای آن برمی گردیم اما این بار item معادل حرف r نیست بلکه یک مرحله به جلو رفته و معادل حرف o خواهد بود (گردش دوم). حالا دوباره دستور print اجرا می شود اما این بار item برابر o بوده بنابراین حرف o را می گیریم. سپس دوباره از اول شروع می کنیم و item این بار برابر حرف x است و الی آخر. این فرآیند آنقدر تکرار می شود تا روی تمام اعضای iterable (در اینجا، رشته roxo) گردش کرده باشیم سپس از دستور for خارج می شویم.

یک مثال دیگر از iterable ها (گردش پذیر ها) لیست ها هستند بنابراین بیایید یک مثال از لیست ها را نیز ببینیم:

for item in ["Amir", "Roxo", 13, "development"]:
    print(item)

با اجرای دستور بالا نتیجه زیر را می گیریم:

Amir
Roxo
13
development

منطق همان منطق مثال قبل است با این تفاوت که به جای کاراکتر های یک رشته، روی اعضای یک لیست گردش می کنیم. set ها چطور؟ آیا آن ها نیز iterable یا گردش پذیر هستند؟

for item in {"Amir", "Roxo", 13, "development"}:
    print(item)

نتیجه:

Roxo
Amir
development
13

بنابراین set ها نیز گردش پذیر هستند. این مسئله برای tuple ها نیز صادق بوده و آن ها نیز گردش پذیر هستند. سوال دیگری که از شما دارم این است که اگر item را خارج از بلوک کدهای for صدا بزنیم چه اتفاقی می افتد؟ به کد زیر توجه کنید:

for item in {"Amir", "Roxo", 13, "development"}:
    print(item)
print(item)

دقت کنید که در این کد دستور دوم print فضای خالی ندارد بنابراین خارج از حلقه for است و تا زمانی که اجرای حلقه for پایان پیدا نکند اصلا به این دستور نمی رسیم. حالا حدس بزنید که پس از پایان اجرای حلقه و اجرای دستور print دوم چه مقداری را خواهیم دید. با اجرای این دستور نتیجه زیر را می گیرید.

development
13
Amir
Roxo
Roxo

در بسیاری از زبان های برنامه نویسی متغیر حلقه ها (در اینجا item) پس از اجرای حلقه از بین می رود و دستوری شبیه به print با خطا روبرو می شود اما همانطور که در نتیجه می بینید، زبان پایتون اینطور نیست و متغیر items را نگه می دارد. اگر یادتان باشد روند اجرای حلقه بدین شکل بود که در هر گردش یکی از اعضای iterable در متغیر item قرار می گرفت تا زمانی که آخرین عضو در item قرار گرفته و حلقه تمام شود. آیا متوجه شدید؟ حلقه که تمام شود item دیگر به روز نشده و روی همان Roxo باقی می ماند بنابراین دستور Roxo در انتها دو بار چاپ شده است: یکی از آخرین گردش حلقه و دیگری از دستور print دوم که خارج از حلقه است.

سوال دیگر اینجاست که آیا می توانیم حلقه ها را درون هم قرار بدهیم؟ پاسخ مثبت است! به کد زیر نگاه کنید:

for item in {"Amir", "Roxo", "programming", "development"}:
	for number in [1,2,3,4,5]:
		print(item + str(number))
	print("داخل اولین حلقه قرار گرفته ایم!")

همانطور که در این کد می بینید ما دو حلقه تو در تو داریم. حلقه اول فقط یک دستور print دارد که می گوید «داخل اولین حلقه قرار گرفته ایم». توجه کنید که print به اندازه for دوم فرورفتگی یا indentation دارد بنابراین هر دو متعلق به for اول هستند. در مرحله بعدی یک for دیگر را داریم که یک دستور print دیگر دارد. این دستور print مقدار item را به همراه number ترکیب کرده و چاپ می کند. البته از آنجایی که اعداد در لیست از نوع int هستند باید آن ها را تبدیل به رشته کنیم تا بتوانیم آن ها را به یک رشته بچسبانیم. نتیجه اجرای این کد به شکل زیر است:

Amir1
Amir2
Amir3
Amir4
Amir5
داخل اولین حلقه قرار گرفته ایم!
programming1
programming2
programming3
programming4
programming5
داخل اولین حلقه قرار گرفته ایم!
Roxo1
Roxo2
Roxo3
Roxo4
Roxo5
داخل اولین حلقه قرار گرفته ایم!
development1
development2
development3
development4
development5
داخل اولین حلقه قرار گرفته ایم!

به عبارتی ما وارد اولین حلقه for می شویم و اولین چیزی که به آن می رسیم یک for دیگر است بنابراین آن را اجرا می کنیم. item در گردش اول Amir است بنابراین Amir با اعداد حلقه دوم (number) ترکیب می شود. در نهایت از حلقه دوم خارج شده و به دستور print می رسیم که می گوید داخل اولین حلقه قرار گرفته ایم. برای درک بهتر می توانیم چند دستور print دیگر را نیز اضافه کنیم:

for item in {"Amir", "Roxo", "programming", "development"}:
	print("داخل اولین حلقه قرار گرفته ایم!")
	for number in [1,2,3,4,5]:
		print("داخل دومین حلقه قرار گرفته ایم")
		print(item + str(number))
		print("پایان یک دور از حلقه دوم")
	print("از اولین حلقه خارج شدیم")

من نتیجه اجرای این کد را برایتان قرار می دهم. برای درک بهتر سعی کنید آن را در کنار کد اصلی گذاشته و به دقت و خط به خط مطالعه کنید:

داخل اولین حلقه قرار گرفته ایم!
داخل دومین حلقه قرار گرفته ایم
development1
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
development2
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
development3
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
development4
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
development5
پایان یک دور از حلقه دوم
از اولین حلقه خارج شدیم
داخل اولین حلقه قرار گرفته ایم!
داخل دومین حلقه قرار گرفته ایم
Amir1
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Amir2
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Amir3
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Amir4
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Amir5
پایان یک دور از حلقه دوم
از اولین حلقه خارج شدیم
داخل اولین حلقه قرار گرفته ایم!
داخل دومین حلقه قرار گرفته ایم
programming1
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
programming2
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
programming3
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
programming4
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
programming5
پایان یک دور از حلقه دوم
از اولین حلقه خارج شدیم
داخل اولین حلقه قرار گرفته ایم!
داخل دومین حلقه قرار گرفته ایم
Roxo1
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Roxo2
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Roxo3
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Roxo4
پایان یک دور از حلقه دوم
داخل دومین حلقه قرار گرفته ایم
Roxo5
پایان یک دور از حلقه دوم
از اولین حلقه خارج شدیم

با توجه به توضیحاتی که دادم و با توجه به این گزارش کامل از روند اجرای حلقه ها حتما متوجه روش کار آن ها شده اید. اما یک سوال بسیار مهم از شما دارم. به نظر شما چرا در نتیجه بالا ابتدا development با اعداد ترکیب شده است!؟ اگر به اولین حلقه for نگاه کنید متوجه می شوید که ما یک set را به آن داده ایم. در فصل قبل توضیح دادم که set ها مانند دیکشنری ها، برخلاف لیست ها، نامنظم هستند یعنی مقادیر آن ها در مموری سیستم شما پراکنده شده است. به همین دلیل است که نتیجه ما گاهی با development و گاهی با Amir شروع می شود. ممکن است نتیجه شما از این نظر با من فرق کند (یعنی کدام کلمه در ابتدا با اعداد (number) ترکیب شود اما روند اجرای کلی آن باید دقیقا مانند نتیجه ای باشد که برایتان قرار داده ام.

در قسمت بعدی بیشتر در مورد حلقه ها و مسائل پیرامون آن ها صحبت خواهیم کرد. ما ابتدا در رابطه با terable ها صحبت می کنیم، سپس یک تمرین را با هم حل خواهیم کرد، سپس به سراغ چند متد/تابع مربوط به حلقه ها رفته و در نهایت با حلقه دوم در زبان پایتون به نام while و دستورات مرتبط با آن آشنا خواهیم شد. از این قسمت به بعد و تا انتهای فصل بعدی تمرینات و مسائل جانبی را دنبال خواهیم کرد.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری پایتون حرفه‌ای توصیه می‌کند:
نویسنده شوید

دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.