بخشِ یکمِ این کتاب مفاهیمِ پایهایِ ضروریای را به شما میآموزد که برای نوشتنِ برنامههای پایتون لازم دارید. بسیاری از این مفاهیم بینِ تمامِ زبانهای برنامهنویسی مشترکاند، پس در سراسرِ زندگیِ شما در جایگاهِ یک برنامهنویس کارآمد خواهند بود.
در
فصل ۱
پایتون را روی رایانهی خود نصب میکنید و اولین برنامهیتان را مینویسید، که پیامِ Hello World! را روی نمایشگر ظاهر میکند.
در
فصل ۲
خواهید آموخت که چگونه اطلاعات را در متغیرها ذخیره و با مقادیرِ عددی و متنی کار کنید.
در دو
فصل ۳
و
فصل ۴
لیستها معرفی میشوند. لیستها میتوانند هر اندازه اطلاعات را که بخواهید در یک متغیر ذخیره کنند. این باعث میشود بتوانید با دادهها به طورِ مؤثرتری کار کنید. قادر خواهید شد با صدها، هزاران و حتی میلیونها مقدار در تنها چند خط برنامه کار کنید.
در
فصل ۵
از دستورِ if برای نوشتنِ برنامههایی استفاده خواهید کرد که با برآورده شدنِ شرطهای خاصی یک جور پاسخ میدهند و در صورتِ برآورده نشدن جورِ دیگر.
فصل ۶
طرزِ بهکارگیریِ دیکشنریهای پایتون را به شما نشان میدهد که برقراریِ ارتباط بینِ قطعاتِ گوناگونِ اطلاعات را ممکن میکند. دیکشنریها، همانندِ لیستها، میتوانند هر اندازه اطلاعات که بخواهید ذخیره کنند.
در
فصل ۷
دریافت کردنِ ورودی را از کاربر فرامیگیرید، به منظورِ تعاملی کردنِ برنامههایتان.
در
فصل ۸
تابعها را ایجاد خواهید کرد. توابع قطعههای نامگذاریشدهای از برنامه برای انجام دادنِ وظیفهای مشخصاند و هر جا که لازم باشد میتوانید آنها را اجرا کنید.
در
فصل ۹
با کلاسها آشنا میشوید، که ساختن مدل از اشیا و موجوداتِ جهانِ واقعی
—مانندِ سگها، گربهها، مردم، خودروها، موشکها، و چیزهای دیگر— را ممکن میسازند، تا برنامهی شما بتواند هر چیزِ واقعی یا انتزاعی را نمایش دهد.
فصل ۱۰
به شما میآموزد چگونه با فایلها کار کنید و مدیریتِ خطاها را برای جلوگیری از خراب شدنِ ناگهانیِ برنامه به عهده بگیرید. دادهها را پیش از بستن برنامه ذخیره و پس از اجرای مجددِ آن بازیابی میکنید. همچنین، در موردِ استثناها در پایتون خواهید آموخت. مدیریتِ استثنا به شما امکان میدهد که خطاها را پیشبینی کنید تا برنامهتان بدونِ دردسرْ خطاها را مدیریت کند.
در
فصل ۱۱
خواهید آموخت که برای بررسیِ اینکه برنامهتان همانطور که انتظار دارید کار میکند یا نه، آزمون طراحی کنید. در نتیجه، میتوانید برنامههای خود را بدونِ نگرانی از برخوردن به اشکالاتِ جدید گسترش دهید. آزمودنِ برنامه یکی از اولین مهارتهایی است که به شما در گذار از سطحِ مقدماتیِ برنامهنویسی به سطحِ متوسط یاری خواهد رساند.
1 - آغاز به کار
در این فصل، اولین برنامهی پایتونِ خود، hello_wotrld.py، را مینویسید. ابتدا، باید بررسی کنید که آیا نسخهی اخیرِ پایتون روی رایانهی شما نصب شده یا نه. اگر اینطور نباشد، آن را نصب میکنید. همچنین، یک ویرایشگرِ متن برای کار کردن با برنامههای پایتون نصب خواهید کرد. ویرایشگرهای متنْ کدِ پایتون را تشخیص میدهند و همانطور که برنامه را مینویسید، بخشهای مختلف را هایلایت و رنگآمیزی میکنند. این به شما کمک میکند تا ساختارِ برنامهتان را راحتتر درک کنید.
1.1 - برپا کردنِ محیطِ برنامهنویسی
پایتون در سیستمعاملهای متفاوت کمی متفاوت عمل میکند، در نتیجه باید ملاحظات اندکی را در نظر داشته باشید. در بخشهای آتی مطمئن میشویم که پایتون بهدرستی در سیستمتان نصب شده است.
نسخههای پایتون
هر زبانِ برنامهنویسی با ظهورِ فکرهای نو و فناوریهای جدید تحول مییابد. توسعهدهندگانِ پایتون هم به طورِ مستمر این زبان را پرتوانتر کردهاند و آن را به سمتِ همهمنظوره شدنِ هرچه بیشتر سوق دادهاند. آخرین نسخهی پایتون هنگامِ نوشتنِ این کتاب ۳.۷ بوده، ولی تمامِ برنامههای کتاب روی نسخهی ۳.۶ و بعد از آن اجرا خواهند شد. در این بخش متوجه میشوید که آیا پایتون روی سیستمِ شما نصب شده و آیا به نصبِ نسخهی جدیدتری نیاز دارید یا نه. پیوستِ آ راهنمای مفصلی است برای نصبِ آخرین نسخهی پایتون بر روی هر کدام از سیستمعاملهای اصلی.
برخی پروژههای قدیمیِ پایتون هنوز از پایتونِ ۲ استفاده میکنند، اما شما از نسخهی ۳ استفاده کنید. اگر پایتونِ ۲ روی سیستمتان نصب شده، احتمالاً برای پشتیبانی از برخی برنامههای قدیمی است که سیستمتان نیاز دارد. ما نسخهی ۲ را همانگونه که هست رها میکنیم و مطمئن میشویم که نسخهی جدیدتری برای کار کردن نصب میکنید.
اجرای یک قطعهکد پایتون
شما میتوانید مفسّر پایتون را در پنجرهی ترمینال اجرا کرده و به کمک آن و بدون این که به ذخیرهسازی یا اجرای یک برنامه کامل نیاز داشته باشید، کدهای کوچک پایتون را امتحان کنید.
در سراسر کتاب قطعهکد ها را به شکل زیر خواهید دید:
نشان »> نشان میدهد که شما در حال کار با ترمینال هستید، متن پررنگشده همان کد شماست که باید آن را بنویسید و با فشردن دکمهی ENTER اجرا کنید. بیشتر مثالهای این کتاب برنامههای کوچک و جامعی هستند که آنها را به جای ترمینال، به کمک ویرایشگر متنی خود اجرا خواهید کرد، زیرا بیشتر کدهای خود را در ویرایشگر متنی مینویسید. امّا گاهی مفاهیمِ پایهای در قالبِ تعدادی قطعهکدنمایش داده میشوند که در یک نشست ترمینال پایتون اجرا میشوند تا مفاهیمِ خاص بهینهتر ارائه شوند. هر گاه مشابه خط ۱در یک لیستِ کد، سه علامت بزرگتری(«<) دیدید بدانید که کدها و خروجی آن در نشست ترمینال اجرا شدهاند. به زودی کدنویسی را در مفسّر پایتون نصبشده بر سیستم شما امتحان میکنیم.
همچنین از یک ویرایشگر متنی برای ساختن یک برنامه ساده به نام Hello World! بهره خواهیم گرفت. نوشتن چنین برنامهای به یک اصل برای یادگیری برنامهنویسی تبدیل شده است. سنّتی قدیمی در دنیای برنامهنویسی وجود دارد که میگوید اگر در اوّلین برنامه در زبان برنامهنویسی جدید، پیامِ Hello World! را روی صفحه چاپ کنید، برای شما خوشیمن خواهد بود. چنین برنامهی سادهای هدفی بسیار جدی را دنبال میکند: اگر این برنامه بر روی سیستم شما به درستی اجرا شود، هر برنامهی پایتون دیگری که بنویسید نیز به همان ترتیب و به درستی اجرا خواهد شد.
دربارهی ویرایشگر Sublime Text
Sublime Text ویرایشگر متنی سادهای است که میتواند روی تمام سیستم عاملهای امروزی نصب شود. Sublime Text این امکان را فراهم میآورد که تقریباً تمام برنامههای خود را به جای ترمینال، مستقیماً از طریق خودِ ویرایشگر اجرا کنید. کد شما در نشستِ ترمینالی اجرا میشود که درون پنجرهی Sublime Text جاسازیشده و دیدنِ خروجی را آسان میکند.
Sublime Text ویرایشگری مناسب برای تازهکارهاست، با این حال بسیاری از برنامهنویسان حرفهای نیز از آن استفاده میکنند. اگر همزمان با یادگیری پایتون به کار با این ویرایشگر عادت کنید، میتوانید هنگامی به سمت پروژههای بزرگتر و پیچیدهتر میروید به استفاده از آن ادامه دهید.
Sublime Text سیاست اعطای مجوز بسیار آزادانهای دارد: شما میتوانید از ویرایشگر تا زمانی که بخواهید به رایگان استفاده کنید، با این حال توسعهدهندگان آن از شما میخواهند درصورتیکه محصول را پسندیدهاید و میخواهید که استفاده از آن همچنان ممکن باشد، مجوز استفاده از آن را بخرید.
پیوست ب اطلاعاتی در مورد دیگر ویرایشگرها در اختیار میگذارد. اگر کنجکاو به استفاده از گزینههای دیگر هستید، همینجا میتوانید به این پیوست مراجعه کرده و نگاهی به آن بیاندازید. اگر میخواهید برنامهنویسی را به سرعت آغاز کنید میتوانید از Sublime Text برای شروع استفاده کنید و به تدریج و با کسب تجربهی برنامهنویسی ویرایشگرهای دیگر را نیز مد نظر قرار دهید. در این فصل به نصب Sublime Text روی سیستم عامل شما میپردازیم.
1.2 - پایتون بر روی سیستم عامل های مختلف
پایتون زبان برنامهنویسی چندسکویی است به این معنا که بر روی تمام سیستم عاملهای اصلی نصب میشود. همهی برنامههای پایتونی که مینویسید روی هر رایانهی امروزی که پایتون داشته باشد، اجرا میشود. با این حال روشهای نصب و راهاندازی پایتون روی سیستمعاملهای مختلف کمی متفاوت است.
در این بخش میآموزید که چگونه پایتون را روی سیستم خود نصب کنید. ابتدا بررسی میکنید که آیا نسخهی متاخری از پایتون روی سیستم شما نصب شده یا نه. اگر چنین نباشد آن را و سپس Sublime Text را نصب میکنیم. این دو گام تنها مراحل متفاوت در سیستم عاملهای مختلفند.
در بخشهایی که در ادامه میآیند شما برنامه Hello World! را اجرا و هر چیزی را که به درستی کار نکرد اصلاح میکنید. این فرآیندِ نصب در هر سیستم عامل بررسی خواهد شد تا بتوانید محیط برنامهنویسی پایتونِ مناسبی برای مبتدیان داشته باشید.
پایتون در ویندوز
پایتون معمولاً به صورت پیشفرض همراه با سیستم عامل ویندوز نصب نمیشود، پس احتمالاً باید آن را نصب کنید و پس از آن به نصب Sublime Text بپردازید.
نصب کردن پایتون
ابتدا بررسی کنید که آیا پایتون بر روی سیستم شما نصب شده یا نه. با وارد کردن عبارت command در منوی استارت یا نگهداشتن دکمه Shift و همزمان کلیکراست کردن روی دسکتاپ و انتخاب Open command window here یک پنجرهی فرمان باز کنید. در پنجره ترمینال عبارت python را با حروف کوچک وارد کنید. اگر در پاسخْ اعلانِ پایتون را (با علامت »>) دریافت کردید، پایتون بر روی سیستم شما نصب شده است. اگر پیامی مبنی بر ناشناخته بودن فرمان python دریافت کردید، به این معنی است که پایتون نصب نشده است.
در آن صورت، یا در صورتی که نسخه پایتون فعلی شما قدیمیتر از ۳.۶ است، باید نصبکنندهی پایتون را برای ویندوز دریافت کنید. به آدرس https://python.org/ بروید و روی پیوند Downloads بایستید. در آنجا باید دکمهای برای نصب آخرین نسخهی پایتون ببینید. بر روی آن کلیک کنید تا دریافت نسخهی درستِ نصبکننده برای سیستم شما به طور خودکار آغاز شود. پس از دریافت فایل آن را اجرا کنید. حتماً گزینهی Add Python to PATH را انتخاب کنید زیرا تنظیم صحیح سیستم شما را سادهتر میکند. شکل ۱-۱ انتخاب این گزینه را نمایش میدهد.
شکل 1: مطمئن شوید که تیک Add Python to PATH را زدهاید.
اجرای پایتون در یک نشست ترمینال
یک پنجرهی فرمان باز کنید و عبارت python را با حروف کوچک وارد کنید. باید اعلان پایتون (»>) را مشاهده نمایید. این اعلان نشان میدهد ویندوز آخرین نسخهی پایتون را که هماکنون نصب کردید، یافته است.
C:\> python
Python 3.7.2 (v3.7.2:9a3ffc0492, Dec 23 2018, 23:09:28) [MSC v.1916 64 bit
(AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
نکته اگر چنین خروجیای یا چیزی شبیه به آن را نمیبینید، دستورالعمل مفصلتر نصب را در پیوست آ ببینید.
خط زیر را در نشست پایتون خود وارد کنید و مطمئن شوید که خروجی Hello Python interpreter را میبینید:
هر زمان که خواستید یک قطعهکد پایتون را اجرا کنید، یک پنجرهی فرمان و یک نشست ترمینال پایتون باز کنید. برای بستن این نشست دکمههای CTRL+Z و سپس ENTER را فشار داده و یا فرمان ()exit را وارد نمایید.
نصب کردن Sublime Text
میتوانید نصبکنندهی Sublime Text را از https://sublimetext.com دریافت کنید. روی پیوند download کلیک کنید و به دنبال نصبکنندهی ویندوز بگردید. بعد از دریافت آن را اجرا کنید و تمام گزینهی پیشفرض پیشنهادی را بپذیرید.
پایتون بر روی macOS
پایتون به صورت پیشفرض بر روی بسیاری از سیستمهای macOS نصب شده امّا به احتمال زیاد نسخهی آن ازردهخارج است و برای یادگیری شما مناسب نیست. در این بخش آخرین نسخهی پایتون و پس از آن Sublime Text را نصب خواهید کرد و از تنظیم شدن صحیح آن مطمئن خواهید شد.
بررسی نصب شدن پایتون ۳
با رفتن به Applications -> Utilities -> Terminal یک پنجرهی ترمینال باز کنید. برای این کار همچنین میتوانید دکمههای و فاصله را فشار داده، عبارت terminal را وارد کرده و ENTER را بزنید. برای فهمیدنِ نسخهی پایتونی که روی سیستم شما نصب شده عبارت python را با p کوچک وارد کنید. این کار یک مفسّر پایتون در ترمینال شما باز میکند که میتوانید از طریق آن دستورهای پایتون را اجرا کنید. اکنون باید خروجیای شامل نسخهی پایتون نصبشده بر روی سیستم شما و یک اعلان با علامت »> ببینید. حالا میتوانید مانند
زیر وارد کردن دستورهای پایتون را شروع کنید:
$ python
Python 2.7.15 (default, Aug 17 2018, 22:39:05)
[GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits", or "license" for more information.
>>>
این خروجی نشان میدهد که در حال حاضر نسخهی پیشفرض نصبشده بر روی سیستم ۲.۷.۱۵ است. پس از این که این خروجی را دیدید، با فشردن Ctrl+D یا وارد کردن ()exit از اعلان پایتون خارج شوید و به اعلان ترمینال برگردید.
برای بررسی این که آیا پایتون ۳ نصب شده است، فرمان python3 را وارد کنید. احتمالاً به خطا برخواهید خورد که به معنای وجود نداشتن پایتون ۳ در سیستم شما است. اگر در خروجی پیامی مشاهده کردید که بیانگر نصب بودن نسخهی ۳.۶ یا جدیدتر پایتون بود، میتوانید این بخش را رها کنید و به بخش «اجرای برنامه در نشست ترمینال» در صفحهی *** بروید. اگر پایتون ۳ به صورت پیشفرض بر روی سیستم نصب نشده بود باید آن را به صورت دستی نصب کنید. توجه داشته باشید که در سراسر این کتاب هر جا دستور python را دیدید باید به جای آن از python3 استفاده کنید تا مطمئن شوید که از پایتون ۳ استفاده میکنید، نه نسخهی ۲ آن. این دو نسخه آنقدر تفاوت دارند که باعث شوند شما در اجرای کدهای این کتاب به کمک پایتون ۲ دچار مشکل شوید.
اگر نسخهای قدیمیتر از ۳.۶ مشاهده کردید دستورالعمل بخش بعد را برای نصب آخرین نسخه پی بگیرید.
نصب کردن آخرین نسخه پایتون
میتوانید نصبکنندهی پایتون را برای سیستم خود در https://python.org بیابید. روی پیوند Download بایستید. باید دکمهای را برای دانلود آخرین نسخهی پایتون ببینید. روی آن کلیک کنید تا دریافت آخرین نسخهی صحیح پایتون به طور خودکار آغاز شود. پس از دریافت فایل نصبکننده آن را اجرا کنید.
پس از پایان یافتن نصب فرمان زیر را در ترمینال وارد کنید:
$ python3 --version
Python 3.7.2
باید خروجیای مشابه این ببینید که نشان میدهد شما آمادهی کار کردن با پایتون هستید. هر جا فرمان python دیدید به جای آن python3 بنویسید.
اجرا کردن پایتون در یک نشست ترمینال
اکنون میتوانید قطعهکدهای پایتون را با باز کردن یک ترمینال و وارد کردن python3 اجرا کنید. خط زیر را در یک نشست ترمینال وارد کنید:
پیام شما باید مستقیماً در پنجرهی فعلی ترمینال چاپ شود. به خاطر داشته باشید که میتوانید مفسّر پایتون را با فشار دادن Ctrl+D یا وارد کردن فرمان exit() ببندید.
نصب کردن Sublime Text
برای نصب ویرایشگر Sublime Text باید فایل نصبکنندهی آن را از https://sublimetext.com/ بگیرید. روی پیوند Download کلیک کنید و نصبکنندهای برای macOS پیدا کنید. پس از گرفتن، آن را باز کنید و آیکون آن را کشیده و داخل پوشه Applications قرار دهید.
پایتون در لینوکس
سیستمهای لینوکسی برای برنامهنویسی طراحی شدهاند و به همین دلیل پایتون بر روی بسیاری از رایانههای لینوکسی نصب شده است. کسانی که لینوکس را نوشته و نگهداری میکنند انتظار دارند که شما روزی برنامهنویسی را از یکجا شروع کنید و شما را به این کار تشویق میکنند. به همین دلیل تنظیمات و موارد مورد نیاز برای نصب و شروع برنامهنویسی بسیار اندکاند.
بررسی نسخهی پایتون
پنجرهی ترمینالی را با اجرا کردن برنامه Terminal روی سیستم خود باز کنید. (در اوبونتو میتوانید دکمههای CTRL-ALT-T را فشار دهید). برای دریافتنِ نسخهی نصبشدهی پایتون، python3 را با p کوچک وارد کنید. درصورتیکه پایتون نصب شده باشد این فرمان مفسّرِ آن را اجرا میکند. باید خروجیای مانند زیر را ببینید که شامل نسخهی نصبشدهی پایتون و اعلان »> است. این اعلان نقطه شروع ورود فرمانهای پایتون است:
$ python3
Python 3.7.2 (default, Dec 27 2018, 04:01:51)
[GCC 7.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>
این خروجی نشان میدهد که در حال حاضر نسخهی ۳.۷.۲ پایتون به عنوان نسخه پیشفرض روی سیستم شما نصب شده است. هرگاه این خروجی را دیدید، با فشردن Ctrl+D یا وارد کردن exit() از اعلان پایتون به اعلان ترمینال برگردید. هر جای این کتاب که دستور python دیدید به جای آن python3 وارد کنید.
برای اجرای کدهای این کتاب به نسخهی ۳.۶ یا جدیدتر پایتون نیاز دارید. اگر نسخهای که روی سیستم شما نصب شده قدیمیتر از ۳.۶ است با مراجعه به پیوست آ آخرین نسخه را نصب کنید.
اجرا کردن پایتون در یک نشست ترمینال
برای اجرا کردن قطعهکدهای پایتون میتوانید مانندِ آنچه هنگام بررسی نسخهی نصبشده انجام دادید، یک ترمینال باز کنید و فرمان python3 را وارد کنید. این کار را مجدداً انجام دهید و خط زیر را در نشست ترمینال وارد کنید:
پیام خروجی باید مستقیماً در پنجرهی فعلی ترمینال چاپ شود. به خاطر داشته باشید که میتوانید مفسّر پایتون را با فشردن Ctrl+D یا وارد کردن فرمان ()exit ببندید.
نصب کردن Sublime Text
در لینوکس میتوانید به کمک Ubuntu Software Center این نرمافزار را نصب کنید. روی آیکون Ubuntu Software کلیک کنید و Sublime Text را جستجو کنید. با کلیک روی آن، آن را نصب و اجرا کنید.
1.3 - اجرای یک برنامهی Hello World
با نصب شدن نسخهی مناسب پایتون و Sublime Text، تقریباً آمادهاید که اوّلین برنامهی پایتونی را در یک ویرایشگر متنی بنویسید و اجرا کنید. امّا پیش از پرداختن به این کار باید مطمئن شوید که در سیستم شما Sublime Text به درستی برای استفاده از نسخهی درست پایتون تنظیم شده یا نه. پس از آن برنامهی Hello World! را نوشته و اجرا میکنید.
تنظیم کردن Sublime Text برای استفاده از نسخهی صحیح پایتون
اگر در سیستم شما فرمان python پایتون ۳ را فرامیخواند نیاز به هیچ تنظیمی ندارید و میتوانید به بخش بعد مراجعه کنید. اما اگر از فرمان python3 استفاده میکنید باید Sublime Text را برای اجرای برنامههای خود با استفاده از نسخهی درست پایتون، تنظیم کنید.
روی آیکون Sublime Text کلیک کنید یا به کمک نوار جستجوی سیستم خود آن را پیدا و اجرا کنید. به Tools→ Build System → New Build System بروید تا یک فایل تنظیمات جدید برای شما گشوده شود. هر آنچه را میبینید پاک کرده و اینها را به جای آن وارد کنید:
این کد Sublime Text را وامیدارد تا هنگام اجرای برنامههای پایتون از فرمان python3 استفاده کند. در پوشهی پیشفرضی که Sublime Text برای ذخیره کردن میگشاید، فایل را با عنوان Python3.sublime-build ذخیره کنید.
اجرای hello_world.py
پیش از نوشتن اولین برنامهی خود پوشهای به نام python_work برای پروژههای خود بسازید. بهترین کار این است که برای نامگذاری فایلها و پوشههای خود از حروف کوچک و زیرخط استفاده کنید زیرا قراردادهای نامگذاری پایتون چنین پیشنهاد میکنند.
Sublime Text را باز کنید.از File -> Save As، یک فایل پایتون خالی به نام hello_world.py در پوشهی python_work ذخیره کنید. پسوند .py به Sublime Text میگوید که کد شما به زبان پایتون نوشته شده و باید آن را چگونه اجرا و متن آن را به روشی کارآمد هایلایت کند.
پس از ذخیره کردن فایل خود خط زیر را در ویرایشگر متنی وارد کنید:
اگر فرمان python روی سیستم شما کار کند، میتوانید برنامهی خود را با انتخاب Tools->Build در منوی برنامه یا فشردن CTRL+B ـــ (+B روی macOS) اجرا کنید. اگر مانند بخش قبل به ناچار تنظیمات پیشفرض برنامه را تغییر دادهاید، گزینهی Python3 را از Tools->Build System انتخاب کنید. پس از آن میتوانید برنامههای خود را با فشردن CTRL+B (ــــ+B) یا از طریق Tools->Build اجرا کنید.
یک صفحهی ترمینال در قسمت پایینی Sublime Text نمایان میشود و خروجی زیر را نمایش میدهد:
اگر چنین خروجیای را ندیدید احتمالاً اشتباهی در برنامهی شما وجود دارد. حروفی را که وارد کردهاید یک به یک چک کنید. آیا اشتباهاً print را با حروف بزرگ ننوشتهاید؟ آیا یکی یا هر دوی پرانتزها یا علامتهای نقل قول دوتایی را فراموش نکردهاید؟ زبانهای برنامهنویسی دستور زبان بسیار مخصوصی دارند و اگر آن را رعایت نکنید به خطا برخواهید خورد. اگر نتوانستید برنامه را اجرا کنید توصیههای بخش بعد را ببینید.
1.4 - اشکالزدایی
اگر نمیتوانید برنامه hello_world.py را اجرا کنید، اینجا چند راه چاره برای آزمودن وجود دارد که در عین حال راهکارهای عمومی خوبی برای حل هر مشکلِ برنامهنویسی است:
اگر در برنامهای خطای مشخّصی وجود داشته باشد، پایتون یک trace-back که نوعی گزارش خطاست، نمایش میدهد. پایتون فایل را میگردد و سعی میکند که خطا را تشخیص دهد. Trace-back را بررسی کنید؛ ممکن است سرنخی به دست دهد که چه مشکلی مانع اجرا برنامه شده است.
از رایانهی خود دور شوید، نفسی تازه کنید و دوباره تلاش کنید. به یاد داشته باشید که دستور زبان در برنامهنویسی بسیار مهم است، پس حتی جا انداختن یک دونقطه، یک علامت نقل قولِ جابهجا یا پرانتزهای نابهجا میتوانند جلوی اجرای برنامه را بگیرند. بخشهای مرتبط این فصل از کتاب را دوباره بخواهید، کد خود را بررسی کنید و سعی کنید مشکل را بیابید.
از نو آغاز کنید. احتمالاً نیازی نیست که هیچ نرمافزاری را پاک کنید امّا ممکن است پاک کردن فایل hello_world.py و ساختن مجدد آن از اول، فایده داشته باشد.
از کس دیگری بخواهید که گامهای ذکرشده در کتاب را در رایانهی شما یا خود بپیماید و به دقت آنچه را انجام میدهد زیر نظر بگیرید. شاید شما یک گام کوچک را جا انداختهاید و کس دیگری آن را پیدا کند.
شخص دیگری را که پایتون میداند پیدا کنید و از او بخواهید که شما را راه بیندازد. اگر پرس و جو کنید احتمالاً کسی را خواهید یافت که نحوهی کار با پایتون را میداند.
دستورالعملهای راهاندازی این فصل در وبسایت همراه این کتاب در نشانی https://nostarch.com/pythoncrashcourse23/ نیز یافت میشود. نسخهی آنلاین این دستورالعملها ممکن است برای شما کارآمدتر باشد زیرا میتوانید به کمک آن و به سادگی کد را کپی کنید.
دنبال کمک آنلاین بگردید. در پیوست پ تعدادی از این منابع مانند انجمنها یا تالارهای گفتگوی آنلاین معرّفی شدهاند که میتوانید در آنها از کسانی که قبلاً با مشکل شما سر و کار داشتهاند کمک بخواهید.
هرگز نگران این نباشید که ممکن است مزاحم برنامهنویسان خبره شوید. هر برنامهنویسی تجربهی گیر کردن در مسالهای را دارد و بیشتر برنامهنویسان از کمک کردن به شما برای برپایی سیستم خود خوشحال میشوند. مادامیکه به روشنی بیان کنید که چه کاری میخواهید بکنید، چه تلاشهایی کردهاید و چه نتایجی گرفتهاید، بخت خوبی خواهید داشت تا کسی را برای یاری گرفتن بیابید. چنانکه در مقدّمه گفتیم، جامعهی پایتون فضایی دوستانه دارد و از تازهکارها استقبال میکند.
پایتون روی رایانههای امروزی به خوبی اجرا میشود. مشکلات اولیه در راهاندازی ممکن است خستهکننده به نظر بیایند ولی میارزد که از آنها سردربیاورید. زمانی که hello_world.py را اجرا کردید، میتوانید آموختن پایتون را آغاز کنید و برنامهنویسی شما جذابتر و لذتبخشتر خواهد شد.
1.5 - اجرا کردن برنامههای پایتون از ترمینال
بیشتر برنامههایی را که در ویرایشگر متنی مینویسید مستقیماً از همانجا اجرا خواهید کرد. با این حال گاهی اجرای برنامهها از ترمینال سودمند خواهد بود. برای مثال ممکن است شما بخواهید برنامهای را که قبلاً نوشته شده بدون باز کردن در ویرایشگر متنی اجرا کنید.
اگر بدانید چگونه به پوشهای که برنامه در آن قرار دارد دسترسی پیدا کنید، میتوانید آن را در هر سیستمی که پایتون بر روی آن نصب شده اجرا کنید. برای امتحان، مطمئن شوید که فایل hello_world.py را در پوشهی python_work در میز کار خود ذخیره کردهاید.
در ویندوز
در یک پنجرهی فرمان میتوانید از دستور ترمینالی cd (سرواژهی change directory) برای گشت و گذار در فایلسیستم خود استفاده کنید. فرمان dir تمام فایلهایی را که در پوشه فعلی وجود دارد نمایش میدهد.
یک پنجرهی ترمینال باز کنید و دستورهای زیر را برای اجرای hello_world.py وارد کنید:
C:\> cd Desktop\python_work
C:\Desktop\python_work> dir
hello_world.py
C:\Desktop\python_work> python hello_world.py
Hello Python world
در خط ۱از دستور cd برای رفتن به پوشهی python_work که خود در پوشهی Desktop قرار دارد استفاده میکنید. بعد از فرمان dir برای اطمینان یافتن از وجود فایل hello_world.py در این پوشه استفاده میکنید.(خط ۲) در ادامه با بهکارگیری فرمان python hello_world.py فایل را اجرا میکنید.(خط ۴)
بیشتر برنامههای شما مستقیماً از ویرایشگر متنی به خوبی اجرا میشوند. امّا همزمان با پیچیدهتر شدن کار، گاهی به اجرای برنامه از یک ترمینال نیاز پیدا میکنید.
در macOS و لینوکس
اجرای یک برنامه پایتون روی لینوکس و macOS یکسان است. میتوانید از دستور ترمینالی cd برای گشتن در فایلسیستم خود در یک پنجرهی فرمان استفاده کنید. فرمان ls تمام فایلهای غیرپنهان را که در پوشه فعلی وجود دارد نمایش میدهد.
یک پنجرهی ترمینال باز کنید و فرمانهای زیر را برای اجرای hello_world.py وارد کنید:
~$ cd Desktop/python_work/
~/Desktop/python_work$ ls
hello_world.py
~/Desktop/python_work$ python hello_world.py
Hello Python world!
در خط ۱ از دستور cd برای رفتن به پوشهی python_work که خود در پوشهی Desktop قرار دارد استفاده میکنید. سپس از فرمان ls برای اطمینان یافتن از وجود فایل hello_world.py در این پوشه استفاده میکنید.(خط ۲) در ادامه با بهکارگیری فرمان python hello_world.py فایل را اجرا میکنید.(خط ۴)
بسیار ساده است. شما از دستور python (یا python3) برای اجرای برنامههای پایتون خود استفاده میکنید.
1.6 - خودآزمایی
تمرینهای این فصل به خودی خود قابل درکند. با شروع فصل ۲، چالشهایی که رفع میکنید بر پایهی چیزهایی است که آموختهاید.
python.org
صفحهی اصلی سایت پایتون را بگردید و موضوعاتی را به نظرتان جالب میآید بیابید. هر چه با پایتون بیشتر آشنا شوید بخشهای مختلف این سایت بیشتر به کار شما خواهند آمد.
Hello World Typos
فایل hello_world.py را که هماینک ساختهاید باز کنید. جایی در خط برنامه یک غلط املایی ایجاد کنید و برنامه را دوباره اجرا کنید. آیا میتوانید غلطی املاییِ مشکلزا را اصلاح کنید؟ آیا پیام خطا برای شما قابل درک است؟ میتوانید غلطی املایی درست کنید که خطایی ایجاد نکند؟ فکر میکنید چرا این غلط برنامه را دچار خطا نکرد؟
Infinite Skills
اگر مهارتهای بیپایان برنامهنویسی داشتید چه برنامهای میساختید؟ شما میخواهید برنامهنویسی را بیاموزید. اگر یک هدف پایانی در ذهن داشته باشید برای مهارتهای جدیدی که میآموزید کاربردی آنی خواهید داشت. الان زمان خوبی است که از چیزهایی که میخواید بسازید پیشنویسی بنویسید. داشتن یک دفترچهی «ایدهها» عادت خوبی است تا برای شروع هر پروژهی جدیدی به آن مراجعه کنید. چند دقیقه وقت بگذارید و سه برنامهای را که میخواید بسازید شرح دهید.
1.7 - خلاصه
در این فصل در مجموع کمی دربارهی پایتون آموختید و اگر آن را روی سیستم خود نداشتید، نصبش کردید. همچنین یک ویرایشگر متنی برای کدنویسی راحتتر پایتون نصب کردید. قطعهکدهای پایتون را در یک نشست ترمینال و اولین برنامهی خود hello_world.py را اجرا کردید. همچنین کمی در مورد نحوهی اشکالزدایی آموختید.
در فصل بعد در مورد انواع مختلف دادهای خواهید آموخت که میتوانید در برنامههای پایتون با آنها کار کنید و از متغیّرها استفاده خواهید کرد.
2 - متغیّرها و انواع سادهی دادهها
در این فصل در مورد انواع مختلفی از دادهها خواهید آموخت که میتوانید با آنها در برنامههای پایتون کار کنید. همچنین چگونگی استفاده از متغیّرها را برای نمایش داده در برنامههای خود یاد میگیرید.
2.1 - هنگام اجرای hello_world.py در واقع چه اتّفاقی میافتد؟
بیاید به آنچه پایتون هنگام اجرای hello_world.py انجام میدهد نگاهی دقیقتر بیاندازیم. برخلاف تصوّر ما پایتون حتی هنگام اجرا کردن یک برنامهی ساده هم کارهای قابل توجّهی انجام میدهد:
هنگامی که فایل hello_world.py را اجرا میکنید، پسوند .py مشخّص میکند که این فایل یک برنامهی پایتون است. پس ویرایشگر متنی این فایل را از طریق مفسّر پایتون اجرا میکند. این مفسّر برنامه را میخواند و مشخّص میکند که هر واژه در برنامه چه معنیای میدهد. برای مثال هنگامی که مفسّر به واژهی print با دو پرانتزِ پس از آن میرسد، هر آنچهکه داخل پرانتزها قرار دارد روی صفحه چاپ میکند.
همچنان که به نوشتن برنامه ادامه میدهید ویرایشگر شما بخشهای مختلف برنامه را به روشهای مختلف هایلایت و رنگآمیزی میکند. برای مثال تشخیص میدهد که print() نام یک تابع است و آن را با یک رنگ نمایش میدهد. همچنین تشخیص میدهد که “Hello Python world!” کد پایتون نیست و آن را با رنگِ دیگری نمایش میدهد. این ویژگی برجستهسازی یا هایلایت نحوی نامیده میشود و برای شروعِ برنامهنویسی بسیار سودمند خواهد بود.
2.2 - متغیّرها
حال بیایید از یک متغیّر در hello_world.py استفاده کنیم. یک خط به ابتدای فایل خود بیفزایید و خط دوم را اینگونه تغییر دهید:
یک متغیّر به نام message اضافه کردهایم. هر متغیّر با یک مقدار در ارتباط است که اطّلاعات مرتبط با آن متغیّر را در بر دارد. در این مورد مقدارْ همان متنِ “Hello Python world!” است.
اضافه کردن یک متغیّر کارِ مفسّر پایتون را کمی سختتر میکند. هنگامی که مفسّر خط اوّل را پردازش میکند متغیّر message را به متن “Hello Python world!” ارتباط میدهد و زمانی که به خطّ دوم رسید مقداری را که به message اختصاص یافته چاپ میکند.
حال با تغییر دادن hello_world.py برای چاپ پیام دیگری، این برنامه را تغییر میدهیم. یک خطّ خالی به فایل خود اضافه کنید و این دو خطّ جدید را اضافه کنید:
در برنامهی خود میتوانید مقدار یک متغیّر را هر زمانی تغییر دهید و پایتون همیشه مقدار فعلی و جدید آن را در نظر خواهد گرفت.
نامگذاری و بهکارگیری مغیّرها
هنگام استفاده از متغیّرها در پایتون باید تعداد اندکی قانون و راهنمایی را رعایت کنید. تخطّی از برخی از این قانونها موجب بروز خطا خواهند شد. راهنماییها به شما کمک میکند تا کدی بنویسید که راحتتر قابل خواندن و فهمیدن باشد. قوانین زیر را در رابطه با متغیّرها حتماً به خاطر داشته باشید:
نام متغیّرها تنها میتواند شامل حروف، اعداد و زیرخط باشند. نامها میتوانند با حرف یا زیرخط و نه با عدد آغاز شوند. برای مثال میتوانید یک متغیّر را message_1 بنامید امّا نامگذاری 1_message ممکن نیست.
گذاشتن فاصله در نام متغیّرها مجاز نیست امّا برای فاصلهگذاری بین چند واژه در نام متغیّرها میتوانید از زیرخط استفاده کنید. برای مثال greeting_message مجاز است امّا greeting message موجب خطا میشود.
از بهکارگیری کلیدواژگان و نام توابع پایتون به عنوان نام متغیّر پرهیز کنید. به بیان دیگر از واژگانی که پایتون برای اهداف خاص برنامهنویسی در نظر گرفته، مانند print، برای نامگذاری استفاده نکنید. (ن.ک «کلیدواژگان و توابع داخلی پایتون» ص ***)
نام متغیّرها بهتر است کوتاه امّا گویا باشند. برای مثال name از n و student_name از s_n و name_length از length_of_persons_name بهتر است.
هنگام استفاده از حرف کوچک l (اِل) و حرف بزرگ O دقّت کنید چون ممکن است با اعداد 1 و 0 اشتباه گرفته شوند.
ممکن است یادگیریِ روشِ ایجاد نامهای مناسب برای متغیّرها کمی به تمرین نیاز داشته باشد، به ویژه زمانی که برنامههای شما جذّابتر و پیچیدهتر میشوند. هر چه برنامههای بیشتری بنویسید و کدهای دیگران را بیشتر بخوانید در یافتن نامهای معنادار پیشرفت حاصل خواهید کرد.
نکته
بهتر است متغیّرهای پایتون را با حروف کوچک بنویسید. استفاده از حروف بزرگ موجب خطا نمیشود امّا بهکارگیری آنها در نام متغیّرها معنای خاصّی دارد که در فصلهای پایانی به آن خواهیم پرداخت.
پرهیز از نامگذاری اشتباه متغیّرها
هر برنامهنویسی اشتباه میکند و بیشتر آنها هر روزه مرتکب اشتباه میشوند. هرچند برنامهنویسان خوب هم ممکن است خطا کنند امّا در عین حال میدانند چگونه به درستی آن را رفع کنند. اجازه دهید به خطایی که احتمالاً به زودی مرتکب میشوید و روش برطرف کردن آن نگاهی بیندازیم.
در اینجا عمداً برنامهای مینویسیم که خطا ایجاد کند. در کد زیر واژهی mesage که پررنگ نشان دادهشده غلط املایی دارد:
هنگامی که در برنامهای خطا رخ میدهد مفسّر پایتون تمام تلاش خود را میکند تا محل وقوع آن را پیدا کند. زمانی که برنامه به درستی اجرا نمیشود مفسّر یک traceback ارائه میکند. هنگامی که مفسّر کد شما را اجرا میکند و در جایی به مشکل میخورد، Traceback پیشینهای از مشکل و محلّ وقوع آن را ذخیره میکند. اینجا نمونهای از traceback میبینیم که پس از اجرای برنامه با نامِ غلطِ متغیّر ایجاد میشود:
Traceback (most recent call last):
File "hello_world.py", line 2, in <module>
print(mesage)
NameError: name 'mesage' is not defined
خروجی خط ۲ وقوع خطایی را در خطِّ دومِ فایلِ hello_world.py گزارش میدهد. مفسّرْ خطِ ۳ را برای کمک به یافتن سریعتر خطا و خط ۴ را برای بیان نوع خطایی که پیدا کرده تولید میکند. در این مورد مفسّر به خطای name error برخورده و اعلام میکند که متغیّر mesage که قرار است چاپ شود، قبلاً جایی تعریف نشده است. پایتون نمیتواند نامی را که به غلط ارائه شده تشخیص دهد. خطای نامگذاری معمولاً به این معنی است که یا مقدار متغیّر را پیش از بهکارگیری آن تعیین نکردهایم یا در وارد کردن نام متغیّر مرتکب اشتباه املایی شدهایم.
میدانیم که در این کد و در خطِ دوم حرف s را در نام متغیّر message جاانداختهایم. مفسّر پایتون کد شما را از لحاظ املایی بررسی نمیکند امّا مطمئن میشود که متغیّرها همواره به درستی نوشته شدهاند. برای مثال ببینید اگر واژهی message را یک جای دیگر برنامه هم اشتباه بنویسیم چه رخ میدهد:
زبانهای برنامهنویسی گرچه سختگیرند امّا به درست یا غلط نوشتن واژگان اهمّیّتی نمیدهند. در نتیجه نیازی نیست که املا و دستور زبان انگلیسی را هنگام نامگذاری متغیّرها و نوشتن کد در نظر بگیرید.
بسیاری از خطاهای برنامهنویسی غلطهای املایی ساده و یککاراکتری در خطّی از برنامه هستند. اگر زمان زیادی را صرف پیدا کردن چنین خطاهایی میکنید بدانید که با بسیاری دیگر همدردید. بسیاری از برنامهنویسان مجرّب و مستعد ساعتها وقت صرف شکار چنین خطاهای کوچکی میکنند. سخت نگیرید و از آنها بگذرید و بدانید که چنین خطاهایی در دوران برنامهنویسی شما بسیار رخ خواهد داد.
متغیّرها برچسب هستند
گاهی متغیّرها را مانند جعبهای در نظر میگیرند که مقادیر را در آنها ذخیره میکنید. دفعات اوّلی که از متغیّرها استفاده میکنید چنین طرز نگاهی سودمند است امّا این روشی دقیق برای بیان نحوهی نمایش داخلی متغیّرها در پایتون نیست. بهتر است به متغیّرها به چشم برچسبهایی نگاه کنید که به مقادیر مختلف میزنید. همچنین میتوانید بگویید که متغیّرها به مقدار مشخّصی اشاره میکنند.
این تمایز احتمالاً در برنامههای آغازین شما خیلی مهم نیست امّا بهتر است آن را زودتر فرابگیرید. ممکن است جایی به رفتار غیرمنتظرهی یک متغیّر بربخورید و درک صحیح و دقیق چگونگی کارکرد متغیّرها به تشخیص آنچه در برنامه رخ میدهد کمک میکند.
نکته
بهترین راه فهمیدن مفاهیم جدید برنامهنویسی به کار بردن آنها در یک برنامه است. اگر هنگام انجام تمرینهای این کتاب به مشکل برخوردید، ابتدا آن را رها کنید و به کار دیگری بپردازید. اگر پس از پرداختن دوباره مشکل حل نشد بخشهای مرتبط با آن را در کتاب مرور کنید. اگر باز هم به کمک نیاز داشتید به توصیههای موجود در پیوست پ مراجعه کنید
خودآزمایی
برای هر تمرین یک برنامهی جداگانه بنویسید. هر برنامه را با نامی ذخیره کنید که مطابق قواعد استاندارد پایتون است، از حروف کوچک و زیرخط استفاده کنید. برای مثال simple_message.py یا simple_messages.py.
Simple Message
پیامی به یک متغیّر نسبت دهید و سپس آن را چاپ کنید.
Simple Messages
پیامی به یک متغیّر نسبت دهید و آن را چاپ کنید. سپس مقدار متغیّر را به پیام جدیدی تغییر دهید و پیام جدید را چاپ کنید.
2.3 - رشتهها
از آنجاییکه بیشتر برنامهها انواعی از داده را تعریف و جمعآوری میکنند و سپس روی آنها عملیّاتی انجام میدهند، دستهبندی انواع مختلف داده سودمند است. اوّلین نوع دادهای که بررسی میکنیم رشتهها هستند. رشتهها در نگاه اوّل ساده به نظر میآیند امّا میتوانید آنها را به روشهای مختلف به کارگیرید.
رشته دنبالهای از حروف یا کاراکترها است. در پایتون هر آنچه داخل علامت نقل قول باشد رشته در نظر گرفته میشود و میتوانید مانند زیر علامت نقل قول تکی یا دوتایی را دور رشته قرار دهید:
'I told my friend, "Python is my favorite language!"'
"The language 'Python' is named after Monty Python, not the snake."
"One of Python's strengths is its diverse and supportive community."
اکنون چند روش استفاده از رشتهها را بررسی میکنیم.
تغییر بزرگ و کوچکی حروف در یک رشته به کمک متدها
یکی از سادهترین کارهای انجام با با رشتهها تغییر دادن وضعیت بزرگ و کوچکی حروف در کلمات آن است. به کد زیر نگاه کنید و سعی کنید آنچه را که رخ میدهد شرح دهید:
در این مثال متغیّر name به رشتهای با حروف آغازین کوچک “ada lovelace” اشاره میکند. هنگام فراخوانی print، متدِ title() پس از متغیّر آمده است. متد عملی است که پایتون میتواند روی قطعهای از داده انجام دهد. نشانهی نقطه (.) پس از name در name.title() به پایتون میگوید که متد title() را روی متغیر name اعمال کند. پس از هر متد یک جفت پرانتز میآید زیرا متدها گاهی به اطّلاعات بیشتری برای انجام کار خود نیاز دارند. چنین اطّلاعاتی داخل این پرانتزها قرار داده میشوند. تابع title() به هیچ اطّلاعات اضافهای نیاز ندارد در نتیجه پرانتزها خالی ماندهاند.
متد title() هر کلمه را به حالت عنوان درمیآورد که در آن هر واژه با حروف بزرگ آغاز میشود. زمانی که به نام به عنوان نوعی اطّلاعات نگاه میکنیم، کاربرد چنین متدی آشکار میشود. برای مثال ممکن است از برنامهی خود بخواهید تمام مقادیر ورودی مانند Ada, ADA و ada را یک نام در نظر بگیرد و همهی آنها را به شکل Ada نمایش دهد.
تعدادی متد سودمند دیگر هم برای تغییر وضعیّت بزرگی و کوچکی حروف وجود دارد. مثلاً میتوانید تمام حروف یک رشته را به حروف بزرگ یا کوچک تبدیل کنید:
متد lower() به ویژه هنگام ذخیرهسازی اطّلاعات سودمند است. در بسیاری از موارد شما نمیخواهید به حروف بزرگ و کوچکی که کاربر وارد کرده اعتماد کنید، پس پیش از ذخیره کردن تمام حروف اطّلاعات ورودی را به حروف کوچک تبدیل میکنید. پس از آن هر زمان که خواستید اطّلاعات را نمایش دهید از حالتی استفاده میکنید که بیشتری معنا را برای هر رشته دارد.
بهکارگیری متغیّرها در رشتهها
مواردی پیش میآید که میخواهید از مقدار یک متغیّر در داخل یک رشته استفاده کنید. برای مثال دو متغیّر دارید که یکی نام کوچک و دیگر نام خانوادگی را نمایش میدهند و میخواهید این دو مقدار را با هم ترکیب کنید تا نام و نام خانوادگی یک نفر را بیان کنید:
برای وارد کردن مقدار یک متغیّر داخل یک رشته، حرف f را مانند ۳ درست پیش از علامت نقل قول آغازین قرار دهید. دور نام یا نامهای هر متغیّری که داخل رشته وارد میشود یک آکُلاد قرار دهید. پایتون هنگام نمایش رشته هر متغیّر را با مقدار آن جایگزین میکند.
به این رشتهها f-string میگویند. f سرواژهی format است، زیرا پایتون این رشتهها را با جایگزینیِ نامِ متغیّرهای داخلِ هر آکُلاد با مقدارِ آن، قالببندی میکند. خروجی کد قبلی به شکل زیر است:
در پیامی که برای تبریک گفتن به شخص ساخته میشود نام کامل او آورده میشود ۴. متد title() حروف اوّل نام کامل را بزرگ میکند. این کد پیامی ساده امّا با قالبی زیبا تولید میکند:
همانند کد قبلی این کد هم پیام Hello, Ada Lovelace! را نمایش میدهد امّا این بار با قرار دادن پیام در یک متغیّر خط ۱ فراخوانی تابع نهاییprint() را بسیار سادهتر کردیم. خط ۲
نکته
f-stringها اوّلین بار در نسخهی ۳.۶ معرّفی شدند. اگر از نسخهی ۳.۵ یا قبل از آن استفاده میکنید باید به جای آن از متد format() استفاده کنید. برای این کار متغیّرهای مورد نظر خود را در پرانتزهایی که پس از format میآیند فهرست کنید. ارجاع به هر متغیّر با یک جفت آکُلاد صورت میپذیرد؛ آکُلادها با مقدارهایی که در پرانتزها قرار داده شدهاند، با همان ترتیب، پر خواهند شد:
در برنامهنویسی به هر حرف چاپنشدنی مانند علامتهای فاصله، تب و پایانِ خط فضای خالی گفته میشود. میتوانید به کمک این فضاهای خالی به خروجی خود شکل دهید تا خواندنِ آن برای کاربر سادهتر باشد.
برای اضافه کردن تب به متن خود از ترکیب حروف \t مانند آنچه در خط ۱ نمایش داده شده استفاده کنید:
>>> print("Languages:\n\tPython\n\tC\n\tJavaScript")
Languages:
Python
C
JavaScript
در دو فصل آینده هنگامی که میخواهید با چند خط کد خطوط زیادی در خروجی تولید کنید، تبها و خطهای جدید بسیار کاربردی خواهند بود.
حذف کردن فاصلههای خالی
در برنامههای شما وجود فاصلهی اضافی در یک رشته میتواند گیجکننده باشد. برای برنامهنویسان دو رشتهی ‘python’ و ‘python ’ تقریباً یکی هستند. امّا برای یک برنامهی رایانهای اینها دو رشتهی متفاوتاند. پایتون فاصلهی اضافی را در ‘python ’ تشخیص داده و تمایز آن را در نظر میگیرد مگر آنکه شما جور دیگری بخواهید.
اهمّیّت فاصلههای خالی آنجا معلوم میشود که گاهی میخواهید دو رشته را برای بررسی یکی بودن مقایسه کنید. یک مورد مهم، برای مثال، مقایسهی نام کاربری کاربران هنگام ورود آنها به یک وبسایت است. فاصلهی اضافی در موارد خیلی سادهتر هم ممکن است گیجکننده باشد. خوشبختانه پایتون حذف کردن فاصلهی خالی را از دادههایی که کاربران وارد میکنند ساده کرده است.
پایتون میتواند فاصلههای خالی را در سمتِ چپ یا راستِ یک رشته تشخیص دهد. برای اطمینان یافتن از عدم وجود فاصلهی خالی در سمت راست یک رشته از متد rstrip() استفاده میکنیم:
در پایان رشتهای که در خط ۱ به عنوان مقدار به favorite_language نسبت دادهایم یک فاصلهی خالی اضافی وجود دارد. وقتی در خط ۲ از پایتون میخواهید که این مقدار را در نشست ترمینال نمایش دهد، فاصله را در انتهای آن میبینید. هنگامی که در خط ۴ متد rstrip() روی متغیّر favorite_language عمل میکند، فاصلهی اضافی حذف میشود. با این حال این حذف شدن موقّتی است. اگر بار دیگر مقدار متغیّر favorite_language را نمایش دهید، خواهید دید که مقدار رشته همانند قبل است و فاصلهی اضافی همچنان وجود دارد. ۶
برای حذف دائمی فاصله از رشته باید مقدار رشتهی اصلاحشده را دوباره به نام همان متغیّر نسبت دهید:
همانطور که در خط ۲ نشان داده شده است، برای حذف فاصلهی اضافی از رشته، آن را از سمت راست رشته حذف میکنید و مقدار جدید را به متغیّر اولیّه نسبت میدهید. تغییر مقدار یک متغیّر در برنامهنویسی بسیار متداول است. اینگونه است که مقدار یک متغیّر میتواند حین اجرای برنامه یا در پاسخ به ورودیِ کاربر تغییر کند.
همچنین میتوانید با متد lstrip() فاصلههای خالی یک رشته را از سمت چپ یا با متد strip() فاصلهها را از هر دو طرف حذف کنید:
در خط ۱ از متغیّری شروع میکنیم که در ابتدا و انتهای آن فاصله وجود دارد. سپس در ۲ فاصلهی اضافه را سمت راست، در ۴ از سمت چپ و در۶ از هر دو طرف پاک میکنیم. تجربهی کار با این متدها شما را نحوهی دستکاری کردن رشتهها آشنا میکند. در دنیای واقعی از این متدها معمولاً برای تمیز کردن اطّلاعات ورودی کاربر پیش از ذخیره کردن آنها استفاده میشود.
پرهیز از خطاهای نحوی هنگام کار با رشتهها
خطای نحوی از جمله ایراداتی است که معمولاً با آن برخورد میکنید. خطای نحوی هنگامی رخ میدهد که پایتون بخشی از برنامهی شما را به عنوان کد معتبر پایتون تشخیص ندهد. برای مثال اگر از آپستروفی داخل علامت نقل تکّی استفاده کنید به خطا خواهید خورد. این اتّفاق به این دلیل میافتد که پایتون هر آنچه بین اوّلین علامت نقل قول و آپستروفی میبیند به عنوان یک رشته تعبیر میکند و پس از آن سعی میکند بقیهی متن را به عنوان پایتون کد تفسیر کند و به مشکل میخورد.
اینجا نحوهی صحیح استفاده از علائم نقل قول تکّی و دوتایی را میبینیم. برنامه را به نام apostrophe.py ذخیره و آن را اجرا کنید:
File "apostrophe.py", line 1
message = 'One of Python's strengths is its diverse community.'
^
SyntaxError: invalid syntax
چنانچه در خروجی میبینید خطا در ۲ درست بعد از علامت نقل قول تکّی دوم رخ داده است. این خطای نحوی نشان میدهد که مفسّر بخشی از کد شما را کد معتبر پایتون نمیداند. خطاها ممکن است منشاهای مختلفی داشته باشند و موارد متداولِ آنها هنگام برخوردن معرّفی خواهد شد. هنگامی که مشغول یادگیری برنامهنویسی پایتون هستید به خطاهای نحوی برخواهید خورد. خطاهای نحوی ناویژهترین و نامشخّصترین انواع خطا هستند در نتیجه تشخیص و اصلاح آنها ممکن است سخت و خستهکننده باشد. اگر به خطای سمجی برخوردید توصیههای پیوست پ را ببینید.
نکته
ویژگی برجستهسازی و هایلایت نحوی ویرایشگرتان میتواند همزمان با نوشتنِ برنامه برخی از خطاهای نحوی را آشکار کند. اگر علامتهای نقل قول را نابهجا به کار ببرید برجستهسازیِ رشته در ویرایشگرها دچار مشکل میشود. اگر ویرایشگر کد پایتون را مانند متن معمولی یا متنی را مانند کد پایتون برجسته کرد معلوم میشود در بهکارگیری علامت نقل قول اشتباه کردهاید.
خودآزمایی
هر کدام از این تمرینها را در فایلی جداگانه با نامی مانند name_cases.py ذخیره کنید. اگر گیر کردید، کمی تامل کنید یا توصیههای پیوست پ را ببینید.
پیام شخصی
متغیّری برای نمایش نام یک شخص تعریف و پیامی خطاب به او چاپ کنید. پیام باید ساده باشد، مثل: “Hello Eric! would you like to learn some Python today?”
بزرگ و کوچک کردن حروف نام
متغیّری برای نمایش نام یک شخص در نظر بگیرید سپس نام او را با حروف بزرگ، کوچک و به صورت عنوان چاپ کنید.
نقل قول مشهور
نقل قولی از شخصی مشهور بیابید که مورد احترامتان باشد. نقل و قول و نام گویندهی آن را چاپ کنید. پیام شما باید شامل علامت نقل قول و چیزی شبیه به این باشد:
Albert Einstein once said, “A person who never made a mistake never tried anything new.”
نقل قول معروف ۲
تمرین ۲-۵ را تکرار کنید امّا این بار نام شخص معروف را با یک متغیّر نمایش دهید. سپس پیام خود را تولید کنید، آن را در یک متغیّر جدید به نام message ذخیره کنید و آن را چاپ کنید.
زدودن فاصله از نامها
متغیّری برای نمایش نام یک فرد در نظر بگیرید و تعدادی فاصلهی خالی به ابتدا و انتهای آن اضافه کنید. از کاراکترهای ترکیبی\t و \n حداقل یک بار استفاده کنید.
یک بار نام را با حروف اضافهی سر و ته آن چاپ کنید. سپس نام را با اعمال کردن توابع lstrip()، rstrip() و strip() چاپ کنید.
2.4 - عددها
اعداد در برنامهنویسی کاربردهای فراوانی دارند، از جمله برای نمایش امتیاز در بازیها، بیان دادهها هنگام بصریسازی، ذخیرهسازی اطّلاعات در نرمافزارهای وب و … . بسته به روشی که عدد مورد استفاده قرار میگیرد پایتون با آن برخوردی متفاوت دارد. ابتدا به نحوهی مدیریت اعداد صحیح در پایتون میپردازیم زیرا سادهترین نوع برای کار کردن هستند.
اعداد صحیح
در پایتون میتوانید اعداد صحیح را جمع (+)، تفریق (-)، ضرب (*) یا تقسیم (/) کنید.
پایتون ترتیب عملیّات را هم رعایت میکند پس میتوانید چند عملیّات را در یک عبارت بگنجانید. همچنین میتوانید از پرانتز برای تعیین تقدّم عملیّات استفاده کنید تا پایتون عبارت را با ترتیبی که شما میخواهید ارزیابی کند.
در پایتون فاصلهگذاری تاثیری بر نحوهی ارزیابی عبارت ندارد و تنها هنگام خواندن کد کمک میکند تا عبارتهایی را که اولویّت بیشتری دارد سریعتر پیدا کنید.
اعداد اعشاری
پایتون هر عدد ممیزدار را به عنوان ممیز شناور (float) میشناسد. این اصطلاح در بسیاری از زبانهای برنامهنویسی به کار میرود زیرا به این حقیقت که ممیز میتواند در هر جای عدد ظاهر شود (=شناور باشد) دلالت دارد. هر زبان برنامهنویسی باید برای مدیریت صحیح اعداد اعشاری به دقّت طرّاحی شود تا صرف نظر از محل قرارگیری ممیّز رفتار آن عدد درست باشد.
در بیشتر موارد بدون نگرانی از عملکرد اعداد اعشاری میتوانید از آنها استفاده کنید. اعداد مورد نظر خود را به راحتی وارد کنید. پایتون به احتمال فراوان همانگونه که انتظار دارید رفتار خواهد کرد:
این اتفاّق در تمام زبانهای برنامهنویسی میافتد و نگرانی چندانی در پی ندارد. پایتون برای یافتن راهی جهت نمایش هر چه دقیقتر نتیجه تلاش میکند امّا این راه ممکن است به سادگی یافت نشود زیرا چگونگی نمایش داخلی اعداد در رایانهها بر آن تاثیر میگذارد. فعلاً کافیست که اعشار اضافی را در نظر نگیرید. هنگامی که در پروژههای بخش دوم کتاب به مدیریّت این بخشهای اعشاری اضافی نیاز داشتیم، این کار را خواهید آموخت.
اعداد صحیح و اعشاری
هنگام تقسیمِ دو عدد صحیح و بخشپذیر که حاصل آن نیز عددی صحیح است، به عددی اعشاری برخواهید خورد:
پایتون هنگام ذخیره کردن این مقادیر زیرخط را نادیده میگیرد. حتّی اگر ارقام را در دستههای سهتایی قرار ندهید، باز هم مقدار تغییر نمیکند. برای پایتون 1000 با 1_000 یا 10_00 تفاوتی ندارد. این ویژگی در اعداد صحیح و اعشاری قابل استفاده است امّا تنها در پایتون ۳.۶ و بعد از آن در دسترس است.
انتساب چندگانه
میتوانید بیشتر از یک متغیّر را تنها در یک خط مقداردهی کنید. این کار برنامهها را کوتاهتر و خواندن آنها را سادهتر میکند. هنگام مقداردهی اولیّهی مجموعهای از اعداد از این روش به مراتب استفاده خواهید کرد.
برای مثال اینجا سه متغیّر x، y و z را با صفر مقدارهای اولیّه میکنیم:
باید نام متغیّرها و همچنین مقدار آنها را با کاما از یکدیگر جدا کنید. پایتون هر مقدار را به متغیّر متناظرش منسوب خواهد کرد. تا زمانی که تعداد مقادیر با تعداد متغیّرها برابر باشد، پایتون آنها را به درستی با هم جور میکند.
ثابتها
ثابت به متغیّری گفته میشود که مقدار آن در طول عمر یک برنامه تغییر نمیکند. پایتون نوع ثابت داخلی و پیشساخته ندارد امّا برنامهنویسان برای متغیّرهایی که ثابت در نظر گرفته میشوند و مقدار آنها هرگز تغییر نمیکند، از نامگذاری با حروف بزرگ استفاده میکنند:
اگر میخواهید در برنامهی خود با متغیّری مثل یک ثابت برخورد کنید نام آن را با تماماً با حروف بزرگ بنویسید.
خودآزمایی
عدد هشت
چهار عملیّات جمع، تفریق، ضرب و تقسیم بنویسید که حاصل همهی آنها عدد ۸ شود. مطمئن شوید که عملیّات را داخل print() قرار میدهید تا نتیجهی آن را ببینید. باید چهار خط مانند زیر بنویسید:
خروجی چهار خط را به سادگی نمایش میدهد که در هر کدام عدد ۸ نمایش داده شده است.
عدد موردعلاقه
متغیّری برای نمایش عدد مورد علاقهی خود تعریف کنید. سپس با استفاده از این متغیّر پیامی تولید و چاپ کنید که عدد مورد علاقهی شما را فاش میکند.
2.5 - توضیحات
افزودن توضیح (یا کامنت) به برنامه در بسیاری از زبانهای برنامهنویسی بسیار سودمند است. تا کنون هر چه در برنامههای خود نوشتهاید کد پایتون بوده است. هر چه برنامه بزرگتر و پیچیدهتر میشود، باید توضیحاتی به آن اضافه کنید تا رویکرد کلّی شما را برای حل مسالهای که در حال حل آن هستید، بیان کند. توضیح در برنامههای شما چنین کاربردی دارد و میتوانید توضیحاتی به زبان انگلیسی به برنامهی خود اضافه کنید.
چگونه یک توضیح بنویسیم؟
علامت هَش (#) در پایتون نمایانگر یک توضیح است. مفسّر پایتون هر آنچه را پس از این علامت بیاید نادیده میگیرد. برای مثال:
دلیل اصلی اضافه کردن توضیحات به برنامه این است که مقصود آن توضیح داده و بگویید که چگونه آن را به کار انداختهاید. هنگامی که شما در جریانِ انجام یک پروژه هستید میدانید که اجزای مختلف چگونه با هم جفت شدهاند. امّا هنگامی که پس از گذشت مدت زمانی به پروژه برمیگردید به احتمال زیاد برخی از جزئیات را فراموش کردهاید. هرچند همیشه میتوانید کد خود را بررسی کنید و بفهمید که قسمتهای مختلف آن چگونه کار میکردهاند، امّا با نوشتن توضیحات مناسب و خلاصه کردن رویکرد کلّی خود به زبان ساده، در زمان صرفهجویی میکنید.
اگر میخواهید به برنامهنویسی حرفهای تبدیل شوید و یا با برنامهنویسان دیگر همکاری کنید باید توضیحات معنادار بنویسید. امروزه بیشتر نرمافزارها را یا گروهی از برنامهنویسان در یک شرکت یا گروهی از مردم در قالب یک پروژهی متنباز به صورت مشارکتی مینویسند. برنامهنویسان باتجربه انتظار دیدن توضیحات در کد دارند پس بهتر است از همین الان نوشتن توضیحات گویا در برنامههای خود را آغاز کنید. نوشتن توضیحات واضح و شفّاف در کد یکی از سودمندترین عادتهایی است که به عنوان یک برنامهنویس جدید میتوانید پیدا کنید.
پیش از تصمیمگیری برای نوشتن یک توضیح به این فکر کنید که آیا برای رسیدن به جواب رویکردهای مختلفی را در نظر گرفتهاید یا نه. اگر این چنین است توضیحی دربارهی راهحل خود بنویسید. این که بعداً چند توضیح اضافی را پاک کنید بسیار سادهتر از اضافه کردن توضیح به کدی است که به خوبی شرح داده نشده است. از الان به بعد از توضیحات در مثالها استفاده خواهد شد تا بخشهای مختلف کد توضیح داده شود.
خودآزمایی
اضافه کردن توضیح
از بین برنامههایی که نوشتهاید دو تا را انتخاب کنید و به هر کدام حداقل یک توضیح اضافه کنید. اگر به دلیل سادگی برنامهها توضیحی برای افزودن نمییابید صرفاً اسم و تاریخ نوشتن کد را بالای برنامه اضافه کنید. سپس یک جمله در مورد هدف برنامه اضافه کنید.
2.6 - ذنِ پایتون
برنامهنویسان باتجربهی پایتون همواره شما را به پرهیز از پیچیدگی و در پی گرفتن سادگی، هر جا که ممکن باشد، تشویق میکنند. فلسفهی جامعهی پایتون در “The Zen of Python” اثر Tom Peters شرح داده شده است. با وارد کردن import this در مفسّر پایتون خود میتوانید این مجموعهی خلاصه از اصول نوشتن کد پایتون خوب را پیدا کنید. کل Zen of Python را اینجا نمایش ندادهایم امّا برای کمک به درک اهمّیّت آن به عنوان برنامهنویس تازهکار پایتون چند خط از آن را میآوریم.
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
برنامهنویسان پایتون این نکته را پذیرفتهاند که کد میتواند زیبا و ظریف باشد. افراد با برنامهنویسی مسائل را حل میکنند. برنامهنویسان همواره راهحلهای بهینه، خوشساخت و حتّی زیبا را ستودهاند. همانطور که پایتون را فرامیگیرید و از آن برای نوشتن برنامههای بیشتری استفاده میکنید، ممکن است روزی کسی روی شانههای شما بایستد و بگوید: «عجب کد زیبایی!»
اگر بین یک راهحل ساده و یک راهحل پیچیده مخیّر بودید و هر دو کار میکرد، ساده را انتخاب کنید. نگهداری از کد شما راحتتر و استفاده از آن برای ایجاد برنامههای دیگر سادهتر خواهد بود.
زندگی واقعی سخت و درهمریخته است و ممکن است راهحلی ساده برای یک مساله دستنیافتنی باشد. در چنین حالتی سادهترین راهحلی را به کار بگیرید که جواب میدهد.
حتّی اگر کد شما پیچیده است تلاش کنید آن را خوانا نگه دارید. زمانی که روی پروژهای با برنامههای پیچیده کار میکنید بر روی نوشتن توضیحات پرمعنی تمرکز کنید.
There should be one-- and preferably only one --obvious way to do it.
اگر از دو برنامهنویس پایتون بخوانید که مسالهای مشابه را حل کنند، نتیجهای که میگیرند باید تا حدود زیادی به یکدیگر بخورد. این به معنی تهی بودن برنامهنویسی از ابتکار و خلّاقیّت نیست. درست برعکس! امّا برنامهنویسی بیشتر استفاده از رویکردهای ساده و مشترک برای رسیدن به شرایطی ساده در یک پروژه ی بزرگتر است. پیچ و مهرهی برنامههای شما باید برای دیگر برنامهنویسان پایتون قابل فهم و استفاده باشد.
میتوانید باقی عمر خود را به آموختن ریزهکاریهای پایتون و در حالت کلی برنامهنویسی بپردازید امّا با این وضع هرگز هیچ پروژهای را به پایان نخواهید رساند. تلاش نکنید که عالیترین کد را بنویسید، بلکه کدی بنویسید که کار کند و سپس تصمیم بگیرید که آیا این کد نیاز به بهبود دارد یا باید آن را رها کرد و به چیز جدیدی پرداخت.
همچنان که به فصل بعدی میروید و پرداختن به موضوعات پیچیدهتر را شروع می کنید این فلسفهی سادگی و شفافیّت را در ذهن داشته باشید.
برنامهنویسان مجرّب کد شما را خواهند پسندید، با کمال میل به شما بازخورد خواهند داد و در پروژههای جذّاب با شما همکاری خواهند کرد.
خودآزمایی
ذن پایتون
import this را در یک ترمینال پایتون وارد کنید و باقی اصول را مرور کنید.
2.7 - خلاصه
در این فصل کار کردن با متغیّرها را آموختید. آموختید که چگونه نام بامعنا برای متغیّرها برگزینید و هنگام برخوردن به خطاهای نحوی و نامگذاری چه کنید. یاد گرفتید که رشتهها چه هستند و چگونه آنها را با حروف کوچک، بزرگ و در حالت عنوان نمایش دهید. بهکارگیری فاصله را برای مرتب کردن خروجیها و طرز حذف کردن فاصلههای اضافی را از قسمتهای مختلف یک رشته آغاز کردید. کار کردن با اعداد صحیح و اعشاری را شروع کردید و کمی با دادههای عددی کار کردید. همچنین نوشتن توضیحات گویا را برای افزایش خوانایی کد خود آموختید. در پایان در مورد فلسفهی ساده نگهداشتن کد تا جای ممکن، هر جا که میسّر داشت، مطالعه کردید.
در فصل ۳ ذخیره کردن مجموعهای از اطّلاعات را در ساختمان دادهای به نام لیست خواهید آموخت و کار کردن با لیست و تغییر دادن اطّلاعات داخل آن را یاد خواهید گرفت.
3 - معرّفی لیستها
در این فصل و فصل بعد ابتدا خواهید آموخت که لیستها چه هستند و سپس کار کردن با اعضای آنها را آغاز میکنید. لیستها امکان ذخیرهسازی مجموعهای از اطّلاعات (چه به تعداد کم چه میلیونها عدد) را در یک مکان فراهم میآورند. لیستها یکی از قدرتمندترین ویژگیهای پایتون هستند که در اختیار برنامهنویسان تازهکار قرار گرفته و بسیاری از مفاهیم مهم برنامهنویسی را به یکدیگر پیوند میدهند.
3.1 - لیست چیست؟
لیست مجموعهای از عناصر با ترتیبی مشخّص است. میتوانید لیستی شامل حروف الفبا، اعداد ۰ تا ۹ یا نام تمام افراد خانوادهی خود درست کنید. شما میتوانید هرچه بخواهید درون یک لیست قرار دهید و عناصر داخل لیست الزاماً ارتباطی با یکدیگر ندارند. از آنجاییکه هر لیست معمولاً بیش از یک عضو دارد بهتر است آن را به صورت جمع نامگذاری کنید، مانند letters، digits یا names.
در پایتون از کروشه ( [] ) برای نمایش یک لیست استفاده میشود و اعضای داخل آن با کمک کاما از یکدیگر جدا میشوند. اینجا مثالی از یک لیست شامل نام چند نوع دوچرخه آورده میشود:
از آنجاییکه این خروجی برای نمایش به کاربران مطلوب نیست، روش دسترسی به تک تک اعضای لیست را در ادامه میبینیم.
دسترسی به اعضای لیست
لیستها مجموعههای مرتّب هستند پس با تعیین جایگاه، یا شاخص عنصرِ مورد نظر برای پایتون، دسترسی به اعضای آنها ممکن است. برای دسترسی به یک عضو لیست، نام آن و پس از آن شاخص عضو در داخل کروشه را بنویسید.
برای مثال اوّلین نوع دوچرخه را از لیستِ bicycles بیرون میکشیم:
این مثال خروجیای همانند مثال قبل تولید میکند، با این تفاوت که حرف اول آن را بزرگ میکند.
شمارهگذاری شاخص از ۰ شروع میشود نه ۱
پایتون اولین عضو یک لیست را در مکان صفرم در نظر میگیرد نه یکم. این مساله در بسیاری از زبانهای برنامهنویسی صادق است و دلیل آن به روش پیادهسازی عملکرد لیستها در سطوح پایینتر برنامهنویسی برمیگردد. اگر به نتایج نامعقولی میرسید بررسی کنید که آیا مرتکب خطای سادهی یکیجابهجا شدهاید یا نه.
دومین عضو لیست شاخص ۱ دارد. با استفاده از این روش شمارش میتوانید هر عضوِ لیست را با کم کردن عدد ۱ از جایگاه واقعی آن در لیست پیدا کنید. برای مثال برای دسترسی به عضو چهارم لیست، شاخص ۳ را فراخوانی کنید.
مثال زیر نام دوچرخهها را با شاخصهای ۱ و ۳ درخواست میکند:
این کد مقدار ‘specialized’ را برمیگرداند. این روش بسیار سودمند است زیرا در بسیاری از موارد شما بدون اطّلاع از تعداد عضوهای لیست میخواهید به آخرین عضو دسترسی پیدا کنید. این قاعده را میتوان به دیگر شاخصهای منفی نیز گسترش داد: شاخص ۲- عضو دو تا مانده به آخر و ۳- سه تا مانده به آخر را برمیگرداند و … .
بهکارگیری مقادیر اعضای یک لیست
میتوانید از اعضای یک لیست به صورت جداگانه درست همانند هر متغیّر دیگری استفاده کنید. برای مثال به کمک f-string میتوانید پیامی بر اساس یکی از عضوهای لیست ساخت.
اکنون با بیرون کشیدن نام اوّلین دوچرخه و به کمک مقدار آن پیامی تولید میکنیم:
برای کسب تجربهی کار کردن مستقیم با لیستهای پایتون برنامههای کوتاه زیر را بنویسید. بهتر است برای مرتب نگهداشتن تمرینهای هر فصل پوشهای جداگانه برای هر یک بسازید .
نامها
نام چند تن از دوستان خود را در لیستی به نام names ذخیره کنید. نام هر کدام از افراد را یک به یک با فراخواندن هر عضو لیست چاپ کنید.
تبریک
تمرین ۳-۱ را تکرار کنید امّا این دفعه به جای چاپ کردن اسمها به تنهایی، پیامی خطاب به آنها بنویسید. متن هر پیام باید یکسان باشد امّا پیام باید برای هر کس شخصیشده باشد.
لیست شخصی
روش حمل و نقل مطلوب خود (موتورسیکلت، ماشین یا …) را در نظر بگیرید، لیستی شامل چند نمونه از آنها درست کنید و عبارتهایی در مورد اعضای این لیست چاپ کنید، مثلاً “I would like to own a Honda motorcycle”.
3.2 - تغییر دادن، افزودن و حذف کردن عضوها
بیشتر لیستهایی که میسازید پویا هستند، به این معنی که ابتدا آن را ایجاد میکنید و سپس حین اجرای برنامه اعضایی را به آن اضافه یا از آن حذف میکنید. برای مثال میخواهید بازیای طرّاحی کنید که در آن بازیکن باید به موجودات بیگانهای در آسمان شلیک کند. میتوانید مجموعهی اولیّهی موجودات بیگانه را در لیستی ذخیره کنید و هر گاه یکی از آنها مورد اصابت قرار گرفت، آن را از لیست حذف کنید. هرگاه موجود جدیدی روی صفحه ظاهر شود، آن را به لیست اضافه میکنید. طول لیست موجودات بیگانه حین برنامه کم یا زیاد میشود.
تغییر دادن اعضای لیست
دستور تغییر دادن یک عضو شبیه به دستور دسترسی به عضوهای لیست است. برای تغییر یک عضو باید نام لیست را به همراه شاخص عضوی که میخواهید تغییر دهید بنویسید و سپس مقدار جدید را به آن نسبت دهید.
برای مثال فرض کنید لیستی از انواع موتورسیکلت داریم میخواهیم اوّلین عضو آن که ‘honda’ است تغییر دهیم. چگونه میتوانیم این کار را انجام دهیم؟
کد خط 1 لیست اولیّه و ‘honda’ را به عنوان اوّلین عضو آن تعریف میکند. خط 3 مقدار اوّلین عضو را به ‘ducati’ تغییر میدهد. خروجی تغییر اوّلین عضو و دستنخوردهمانده ماندن باقی اعضا را نشان میدهد:
نه فقط اوّلین عضو بلکه تمام اعضای یک لیست را میتوان تغییر داد.
افزودن عضو به لیست
ممکن است به دلایل مختلف بخواهید عضو جدیدی به یک لیست بیفزایید. برای مثال شاید بخواهید یک موجود بیگانهی جدید را به یک بازی، دادهای جدید را برای بصریسازی یا عضو ثبتنامشدهی جدیدی را به یک وبسایت اضافه کنید. پایتون چندین روش برای افزودن دادهی جدید به لیستهای موجود فراهم آورده است.
الحاق کردن اعضا به انتهای یک لیست
سادهترین روش برای افزودن یک عضو الحاق کردن آن به لیست است. با این کار عضو به انتهای لیست اضافه میشود. با استفاده از لیستی که در مثال قبل ساختیم، یک عضو جدید به نام ‘ducati’ به انتهای آن اضافه میکنیم:
متد append() ساختن لیستها به روش پویا را ساده میکند. برای مثال میتوانید یک لیست خالی ایجاد کنید و سپس اعضا را یک به یک به آن بیفزایید. اکنون عضوهای ‘honda’، ‘yamaha’ و ‘suzuki’ را به یک لیست خالی اضافه میکنیم:
ساختن لیست به این روش بسیار متداول است زیرا معمولاً تا پیش از اجرای برنامه نمیدانید که کاربران چه دادهای را میخواهند ذخیره کنند. برای این که اختیار را به دست کاربران بسپرید ابتدا یک لیست خالی تعریف کنید و سپس مقادیری را که وارد میکنند به آن اضافه کنید.
افزودن عضوها داخل لیست
میتوانید به کمک متد insert() عضو جدیدی را به هر مکانی از لیست اضافه کنید. این کار با مشخّص کردن شاخص و مقدار عنصر جدید انجام میشود:
در این مثال کد خط 2 مقدار ‘ducati’ را به ابتدای لیست اضافه میکند. متد insert() در مکان صفرم فضایی خالی ایجاد میکند و مقدار ‘ducati’ را در آن ذخیره میکند. این عمل مکان تمام مقادیر دیگر را یکی به راست منتقل میکند:
گاهی میخواهید که یک عضو یا اعضایی را از یک لیست حذف کنید. برای مثال زمانی که بازیکن یک موجود بیگانه را مورد هدف قرار داده و نابود میکند، احتمالاً میخواهید نام آن موجود را از لیست موجودات فعّال حذف کنید. یا هنگامی که کاربری تصمیم میگیرد عضویّت خود را در برنامهی وبی که ساختهاید حذف کند، میخواهید آن کاربر را از لیست کاربران فعّال حذف کنید. شما میتوانید یک عضو را بر اساس جایگاه یا بر اساس مقدار آن حذف کنید.
حذف کردن یک عضو به کمک عبارت del
اگر مکان عضوی را که میخواهید حذف کنید از پیش میدانید، میتوانید از عبارت del استفاده کنید.
در هر دو مثال، پس از به کارگیری del و حذف شدن عضو، دیگر به مقدار آن دسترسی نخواهید داشت.
حذف کردن یک عنصر به کمک متد pop()
گاهی اوقات پس از حذف کردن یک عضو از لیست به مقدار آن نیاز دارید. مثلاً شاید برای نمایش انفجار روی صفحهی بازی، موقعیّت x و y موجود بیگانهای را که از بین رفته نیاز داشته باشید. یا در برنامهی وب ممکن است بخواهید نام کاربر حذفشده را از لیست کاربران فعّال کم کرده و به لیست کاربران غیرفعّال اضافه کنید.
متد pop() آخرین عضو یک لیست را حذف میکند امّا اجازه میدهد با مقدار حذف شده پس از حذف کار کنید. عبارت pop حاصل نگاه کردن به لیست به عنوان یک پشته و برداشتن بالاترین عضو آن است. در این همانندسازیْ بالای پشته همان انتهای لیست است.
در خط 1 لیست motorcycles را تعریف میکنیم. در خط 3 مقداری را از انتهای لیست برداشته و در متغیّری به نام popped_motorcycle ذخیره میکنیم. در 4 لیست را چاپ میکنیم تا ببینیم که این عضو از آن حذف شده است یا نه. در 5 با چاپ مقدار برداشته شده اطمینان حاصل میکنیم که همچنان به مقدار آن دسترسی داریم.
خروجی نشان میدهد که مقدار ‘suzuki’ از انتهای لیست حذف شده و در متغیّری به نام popped_variable ذخیره شده است:
متد pop() کی سودمند است؟ فرض کنید که موتورسیکلتها به ترتیبِ تاریخِ خریده شدن در لیست ذخیره شدهاند. در این مورد میتوانید از متد pop() جملهای در مورد آخرین موتورسیکلتی که خریدهایم چاپ کنیم:
motorcycles = ['honda', 'yamaha', 'suzuki']
first_owned = motorcycles.pop(0)
print(f"The first motorcycle I owned was a {first_owned.title()}.")
ابتدا در 2 موتور سیکلت اول را از لیست برمیداریم و سپس پیامی در مورد آن در 3 چاپ میکنیم. خروجی دربارهی اوّلین موتورسیکلتی که داشتهام جملهای ساده چاپ میکند:
به خاطر داشته باشید که هر بار از pop() برای عضوی استفاده میکنید، آن را برای همیشه از لیست حذف میکنید.
اگر در بین بهکارگیری del و pop() مردّد هستید راه سادهای برای تصمیمگیری وجود دارد: هرگاه دیگر به هیچعنوان به یک عضو احتیاج نداشتید و خواستید آن را حذف کنید از del استفاده کنید امّا اگر خواستید از مقدار آن پس از حذف شدن بهره بگیرید متد pop() را به کاربگیرید.
حذف کردن یک عضو بر اساس مقدار
گاهی مکان عضوی را که میخواهید حذف کنید نمیدانید. اگر میخواهید تنها با دانستن مقدار یک عضو آن را از حذف کنید میتوانید از متد remove() استفاده کنید.
برای مثال فرض کنید میخواهیم مقدار ‘ducati’ را از لیست موتورسیکلتها حذف کنیم:
motorcycles = ['honda', 'yamaha', 'suzuki', 'ducati']
print(motorcycles)
too_expensive ='ducati'motorcycles.remove(too_expensive)
print(motorcycles)
print(f"\nA {too_expensive.title()} is too expensive for me.")
پس از تعریف لیست در 1، مقدار ‘ducati’ را در 3 به متغیّری به نام too_expensive منسوب کردیم. سپس در خط 4 از پایتون خواستیم که این مقدار را به کمک این متغیّر از لیست حذف کند. در خط 6 مقدار ‘ducati’ از لیست حذف شده امّا مقدار آن به کمک متغیّر too_expensive در دسترس است و میتوانیم به کمک آن پیامی در مورد دلیل حذف شدن ‘ducati’ از لیست چاپ کنیم:
['honda', 'yamaha', 'suzuki', 'ducati']
['honda', 'yamaha', 'suzuki']
A Ducati is too expensive for me.
نکته
متد remove() اوّلین مورد وقوع مقداری را که مشخّص کردهاید حذف میکند. اگر این امکان وجود دارد که یک مقدار چند بار تکرار شده باشد ، از یک حلقه برای اطمینان یافتن از حذف تمامی موارد آن مقدار استفاده کنید. چگونگی این کار را در فصل هفتم خواهید آموخت.
خودآزمایی
تمرینهای زیر نسبت به فصل ۲ کمی پیچیدهترند امّا به شما این فرصت را میدهند تا از لیستها به تمام روشهایی که توضیح داده شد استفاده کنید.
لیست مهمان
اگر بخواهید کسی را، خواه زنده یا مرده، برای شام دعوت کنید، چه کسی را انتخاب میکنید؟ لیستی شامل نام سه نفر از چنین افرادی درست کنید و به کمک آن خطاب به هر یک دعوتنامهای چاپ کنید.
تغییر دادن لیست مهمانان
همین الان متوجه شدید که یکی از مهمانها نمیتواند برای شام بیاید، در نتیجه به ارسال دعوتنامههای جدید نیاز پیدا کردید. باید کس دیگری را برای مهمانی پیدا کنید.
از برنامه تمرین ۳-۴ شروع کنید.یک تابع print() اضافه کنید و در پایان برنامه نام شخصی را که نمیتواند به مهمانی بیاید چاپ کنید.
لیست را تغییر دهید و نام کسی را که دعوت را نپذیرفته با نام مهمان جدید جایگزین کنید.
دستهی دوم دعوتنامه را برای هر یک از افرادی که هنوز در لیست هستند چاپ کنید.
مهمانهای بیشتر
میز شام جدیدی پیدا کردهاید و اکنون جا برای مهمانهای بیشتری باز شده است. سه فرد جدید برای دعوت کردن بیابید.
از برنامه تمرین ۳-۴ یا ۳-۵ شروع کنید. یک تابع print() به انتهای برنامه بیفزایید و به همه اطّلاع دهید که میز شام بزرگتری پیدا کردید.
با استفاده از insert() یک مهمان جدید به ابتدای لیست بیفزایید.
به کمک insert() مهمانی جدید به میانهی لیست اضافه کنید.
از append() بهره بگیرید و مهمان جدید دیگری را به انتهای لیست اضافه کنید.
یک دعوتنامه جدید برای هر یک از افراد حاضر در لیستِ خود چاپ کنید.
کوچک کردن لیست مهمان
مطّلع شدهاید که میز شام جدید به موقع نمیرسد و تنها برای دو مهمان جا دارید.
از برنامه ۳-۶ شروع کنید خط جدیدی به آن اضافه و پیامی چاپ کنید که تنها میتوانید دو مهمان دعوت کنید.
برای حذف کردن مهمانها، چند بار از pop() استفاده کنید تا تنها دو مهمان در لیست باقی بماند. هر بار که نامی را از لیست حذف میکنید پیامی خطاب به آن فرد چاپ کنید و از او به دلیل مشکل پیش آمده عذر بخواهید.
پیامی خطاب به دو مهمان باقیمانده در لیست چاپ کنید و به آنها اطّلاع دهید که همچنان دعوت هستند.
با استفاده از del دو نام باقیمانده را هم از لیست حذف کنید تا به یک لیست خالی برسید. لیست خود را چاپ کنید تا مطمئن شوید که در پایان برنامهی خود یک لیست خالی باقیمانده است.
3.3 - سازماندهی یک لیست
گاهی لیستهای شما با ترتیبی غیر قابل پیشبینی ساخته میشوند زیرا نمیتوانید همواره ترتیبی را که کاربران بر اساسِ آن دادهها را وارد میکنند کنترل کنید. شما معمولاً میخواهید دادهها را با ترتیبی مشخّص نمایش دهید هرچند در بسیاری از شرایط عملاً مجبور به این کار هستید. گاهی میخواهید ترتیب اصلی دادهها را حفظ کنید و گاهی آن را تغییر میدهید. پایتون بسته به شرایط چند روش مختلف برای سازمان دادن به لیستها ارائه میکند.
مرتّب کردن دائمی یک لیست به کمک sort()
متد sort() پایتون مرتّب کردن لیستها را نسبتاً ساده میکند. فرض کنید لیستی از خودروها داریم و میخواهیم ترتیب لیست را تغییر بدهیم تا آنها را به ترتیب حروف الفبا ذخیره کنیم. برای این که کار راحتتر باشد فرض کنید تمام مقادیر لیست با حروف کوچک نوشته شدهاند.
متد sort() که در 2 نمایش داده شده ترتیب لیست را به صورت دائمی عوض میکند. خودروها اکنون دیگر به ترتیب الفبایی هستند و نمیتوانیم آنها را به ترتیب اولیّه برگردانیم:
برای مرتّب کردن به ترتیب عکس (از حروف بزرگ به کوچک) میتوانید نشانوند reverse=True را به متد sort() بدهید. این مثال لیست خودروها را به ترتیب الفبایی معکوس مرتّب میکند:
برای حفظ کردن ترتیب اصلی لیست یا تنها نمایش دادن آن با ترتیبی مشخّص، میتوانید از تابع sorted() استفاده کنید. این تابع کمک میکند که لیست را با ترتیبی خاص صرفاً نمایش دهید امّا بر ترتیب اصلی آن تاثیری نمیگذارد.
حال این تابع را بر روی لیست خودروها اعمال میکنیم:
cars = ['bmw', 'audi', 'toyota', 'subaru']
print("Here is the original list:")
print(cars)
print("\nHere is the sorted list:")
print(sorted(cars))
print("\nHere is the original list again:")
print(cars)
ابتدا در2 لیست را با ترتیب اصلی و سپس در 4 به ترتیب حروف الفبا چاپ میکنیم. پس از نمایش لیست مرتّب، در ۳ نشان میدهیم که ترتیب اصلی لیست دستنخورده باقی مانده است.(6)
Here is the original list:
['bmw', 'audi', 'toyota', 'subaru']
Here is the sorted list:
['audi', 'bmw', 'subaru', 'toyota']
Here is the original list again:
['bmw', 'audi', 'toyota', 'subaru']
دقّت کنید که در 5 و پس از بهکارگیری تابع sorted() لیست همچنان به ترتیب اصلی خود باقیمانده است. تابع sorted() نیز نشانوند reverse=True را برای مرتّب کردن معکوس الفبایی میپذیرد.
نکته
اگر تمام مقادیر با حروف کوچک ذخیره نشده باشند، مرتّب کردن الفبایی لیست کمی مشکلتر میشود. هنگام تعیین کردن ترتیب مرتّبسازی، راههای مختلفی برای تفسیر حروف بزرگ وجود دارد و تعیین ترتیب دقیق ممکن است اکنون برای ما کمی پیچیده باشد. با این حال بیشتر رویکردهای مرتّب سازی که مورد بررسی قرار خواهد گرفت بر اساس چیزی است که در این بخش آموختهاید.
چاپ کردن لیست با ترتیب معکوس
برای عکس کردن ترتیب اصلی یک لیست میتوانید از متد reverse() استفاده کنید. اگر از ابتدا لیست خودروها را به ترتیب زمانِ خریدن مرتّب کرده باشیم، میتوانیم به سادگی لیست را به ترتیبِ زمانیِ معکوس بازآرایی کنیم:
استفاده از len() هنگامی سودمند است که بخواهید تعیین کنید در بازی چند موجود بیگانه برای کشتن باقیمانده است، تعداد دادههایی که برای بصریسازی با مدیریت شوند کدام است یا تعداد کاربران ثبت نام کرده در وبسایت چندتاست.
نکته
استفاده از len() هنگامی سودمند است که بخواهید تعیین کنید در بازی چند موجود بیگانه برای کشتن باقیمانده است، تعداد دادههایی که برای بصریسازی با مدیریت شوند کدام است یا تعداد کاربران ثبت نام کرده در وبسایت چندتاست.
خودآزمایی
تماشای جهان
به پنج نقطه از جهان فکر کنید که دوست دارید آنها از نزدیک ببینید.
این مکانها را در لیستی ذخیره کنید. مطمئن شوید که ترتیب ذخیره الفبایی نباشد.
لیست را با ترتیب اصلی چاپ کنید. نگران چاپ کردن تمیز و منظم نباشید و لیست را به صورت یک لیست خام پایتون چاپ کنید.
از sorted() برای چاپ لیست به ترتیب الفبایی و بدون دست خوردن آن استفاده کنید.
نشان دهید که لیست شما همچنان با ترتیب اصلی ذخیره شده است.
از sorted() برای مرتّبسازی الفبایی معکوس بدون دست زدن به ترتیب لیست اصلی استفاده کنید.
نشان دهید که لیست شما همچنان با ترتیب اصلی ذخیره شده است.
از reverse() برای معکوس کردن ترتیب اعضای لیست استفاده کنید. آن را چاپ کنید تا نشان دهید که ترتیب عوض شده است.
دوباره از reverse() برای معکوس کردن لیست استفاده کنید. نشان دهید که لیست به ترتیب اصلی خود برگشته است.
از sort() برای مرتب کردن لیست به ترتیب الفبا استفاده کنید.آن را چاپ کنید تا نشان دهید ترتیب عوض شده است.
از sort() برای مرتب کردن لیست به ترتیب الفبایی معکوس استفاده کنید. آن را چاپ کنید تا نشان دهید ترتیب عوض شده است.
مهمانهای شام
با کار بر روی یکی از تمرینهای ۳-۴ تا ۳-۷ در صفحهی ***، به کمک len() پیامی چاپ کنید که تعداد افراد دعوتشده را نمایش دهد.
تمام تابعها
به چیزی که بتوان آن را در لیستی ذخیره کرد فکر کنید. برای مثال فهرست کوهها، رودخانهها، کشورها، زبانها یا هر چیزی که دوست دارید. برنامهای بنویسید که لیستی شامل این موارد ایجاد کند و تمام توابعی را که در این فصل آموختهاید یک بار روی آن اعمال کنید.
3.4 - پرهیز از خطای شاخص هنگام کار کردن با لیستها
هنگامی که برای اولین بار با لیستها کار میکنید عموماً به نوعی خطا برمیخورید. فرض کنید لیستی شامل سه عضو داریم و عضو چهارم آن را فرامیخوانیم:
Traceback (most recent call last):
File "motorcycles.py", line 2, in <module>
print(motorcycles[3])
IndexError: list index out of range
پایتون برای یافتن عنصری با شاخص ۳ تلاش میکند امّا پس از جستجو عضوی با این شماره پیدا نمیکند. به دلیل ماهیت یکیجابهجای شمارهگذاری در لیستها این خطا متداول است. همه فکر میکنند که شمارهی سومین عضو ۳ است زیر شمارش را از ۱ شروع میکنند. امّا پایتون شاخص سومین عضو را ۲ میپندارد زیرا شمارش را از ۰ شروع میکند.
خطای شاخص به این معنی است که پایتون نمیتواند عضوی با شاخصی که معلوم کردهاید پیدا کند. اگر در برنامهتان به این خطا برخوردید شاخص را یکی جابهجا کنید. برنامه را مجدداً اجرا کنید و ببینید آیا نتایج درست است یا نه.
به خاطر داشته باشید که همیشه میتوانید با بهکارگیری شاخص ۱- به آخرین عضو لیست دسترسی پیدا کنید. این مورد همیشه کار میکند حتّی اگر طول لیست شما از آخرین باری که به آن دسترسی داشتهاید تغییر کرده باشد:
Traceback (most recent call last):
File "motorcyles.py", line 3, in <module>
print(motorcycles[-1])
IndexError: list index out of range
نکته
اگر خطای شاخص رخ میدهد و نمیتوانید بفهمید که چگونه آن را برطرف کنید لیست خود یا تنها طول آن را چاپ کنید. لیست شما ممکن است با آنچه میپنداشتید خیلی تفاوت داشته باشد، به ویژه در مواردی که آن را به صورت پویا در برنامه مدیریت میکنید. دیدن لیست واقعی یا تعداد دقیق اعضای آن میتواند به سردرآوردن از این خطاهای منطقی کمک کند.
3.5 - خلاصه
در این فصل آموختید که لیستها چه هستند و با تک تک اعضای آنها چگونه میتوان کار کرد. یاد گرفتید که چگونه لیست را تعریف و عناصری به آن اضافه یا از آن حذف کنید. مرتّب کردن دائمی و موقّت لیستها را جهت نمایش آموختید. همچنین نحوهی یافتن طول یک لیست و چگونگی پرهیز از خطاهای شاخص را هنگام کار با لیستها فراگرفتید.
در فصل ۴ خواهید آموخت که با اعضای یک لیست چگونه بهینهتر کار کنید. تنها با چند خط و با حلقه زدن و بررسی تک تک اعضای لیست، میتوانید بهینهتر عمل کنید؛ حتّی اگر لیست شما شامل هزاران یا میلیونها عضو باشد.
4 - کار کردن با لیستها
در فصل سوم آموختید که چگونه یک لیست ساده درست کنید و با تک تک اعضای آن کار کنید. در این فصل میآموزید که چگونه فارغ از طول یک لیست، تنها با چند خط کد در کلِ آن حلقه بزنید. حلقه زدن این امکان را فراهم میآورد که عمل یا مجموعهای از عملیّاتِ یکسان را روی هر عضو یک لیست اعمال کنید. در نتیجه میتوانید با لیستهایی با هر طول، حتّی آنها که شامل هزاران یا میلیونها عضوند، به طور موثر کار کنید.
4.1 - حلقه زدن در کل یک لیست
بیشتر اوقات میخواهید با رجوع به تکتکِ اعضای یک لیست، کاری مشابه را روی هر کدام انجام دهید. برای مثال در یک بازی ممکن است بخواهید که هر عنصر را روی صفحه به یک اندازه تکان دهید یا بر روی اعضای یک لیست عددی محاسبهی آماریِ یکسانی انجام دهید. یا بخواهید سرخطّهای لیستی از مقالهها را روی وبسایتی نمایش دهید. هر گاه بخواهید عملی یکسان را بر روی تمام اعضای یک لیست انجام دهید میتوانید از حلقهی for پایتون بهره بگیرید.
فرض کنید که لیستی از اسامی تعدادی شعبدهباز در اختیار داریم و میخواهیم اسم تمامِ آنها را چاپ کنیم. این کار را میتوان با بازیابیِ جداگانهی هر عضوِ لیست انجام داد ولی این رویکرد چند مشکل ایجاد میکند. یکی این که ممکن است در لیستهای طولانی تکراری به نظر برسد. جز این، ما مجبوریم با هر بار تغییر یافتنِ طول لیست کد خود را تغییر دهیم. حلقهی for هر دو مشکل را با سپردن مدیریت این کار به صورت داخلی به پایتون برطرف میکند.
اکنون از حلقهی for برای چاپ کردن تکتک نامها در لیست شعبدهبازها استفاده میکنیم:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician)
در خط ۱ همانند کاری که در فصل سوم کردیم، از تعریف یک لیست شروع میکنیم. در خط ۲ یک حلقهی for تعریف میکنیم. این خط از پایتون میخواهد تا یک نام را از لیست magicians بیرون کشیده و آن را به متغیّر magician نسبت دهد. در خط ۳ به پایتون میگوییم که نامی را که هماکنون به magician نسبت داده چاپ کند. پایتون سپس خطهای ۲ و ۳ را برای تمام نامهای داخل لیست تکرار میکند. شاید خواندن این کد به این صورت برای شما سودمند باشد: «برای هر شعبدهباز در لیست شعبدهبازها، نام شعبدهباز را چاپ کن.» در خروجی به سادگی چاپ شدن هر نام موجود در لیست را خواهیم دید:
مفهوم حلقه زدن بسیار مهم است زیرا یکی از معمولترین راهها برای خودکارسازی وظایف تکراری با رایانه است. به عنوان یک مثال در حلقهی سادهای که در magicians.py به کار بردیم، پایتون ابتدا اوّلین خط حلقه را میخواند:
این خط به پایتون میگوید که اوّلین مقدار را از لیست magicians بیرون بکشد و آن را به متغیّر magician نسبت بدهد. اوّلین مقدار ‘alice’ است. پایتون سپس خط بعدی را میخواند:
پایتون مقدار فعلی magician را که همچنان ‘alice’ است چاپ میکند. از آنجایی که لیست مقادیر دیگری را نیز دربرمیگیرد، پایتون به اوّلین خط حلقه بازمیگردد:
پایتون بار دیگر مقدار magician را چاپ میکند که الان ‘david’ است و بار دیگر کل حلقه را تا فرارسیدنِ آخرین مقدارِ درون لیست، ‘carolina’، تکرار میکند. چون مقدار دیگری در لیست وجود ندارد پایتون به خط بعد از حلقه میرود. در این مورد چون بعد از حلقهی for چیزی وجود ندارد برنامه به سادگی پایان مییابد.
هنگامی که برای نخستین بار از حلقهها استفاده میکنید به خاطر داشته باشید که مجموعهای از گامها و مراحل برای تکتک اعضای لیست یکبار تکرار میشود و تعداد اعضای لیست مهم نیست. اگر یک میلیون مورد در لیست شما باشد پایتون تمام این مراحل را یک میلیون بار تکرار میکند، که معمولاً سریع هم انجام میگیرد.
همچنین بدانید که هنگام نوشتن حلقههای for، برای متغیّر موقّت حلقه که هر مقدارِ درون لیست به آن منسوب میشود، میتوانید هر اسم دلخواهی را انتخاب کنید. با این حال انتخاب کردن نامی معنادار که بیانگر یک عضو لیست باشد سودمند است. برای مثال راه مناسب برای نوشتن یک حلقهی for برای لیست گربهها، سگها و یک لیست عمومی از اقلام در اینجا میآید:
for cat in cats:
for dog in dogs:
for item in list_of_items:
این قراردادها در نامگذاری به شما کمک میکند تا اتّفاقاتی را که در داخل حلقهی for برای هر عضو آن میافتد دنبال کنید. بهکارگیری اسمهای مفرد و جمع کمک میکند که بفهمید یک بخش کد روی یک عضو لیست کار میکند یا تمام آن.
4.2 - کارهای بیشتر در یک حلقهی for
در داخل یک حلقه for میتوانید هر کاری که بخواهید با تکتک عضوها انجام دهید. بر اساس مثال قبلی کاری میکنیم که پیامی خطاب به هر شعبدهباز صادر شود و به او گفته شود که شعبدهی خوبی اجرا کرده است:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
تنها تفاوت این کد در خط ۱ است که در آن پیامی خطاب به هر شعبدهباز چاپ کردیم که با نام او آغاز میشود. در اوّلین گام حلقه مقدار magician برابر با ‘alice’ است پس پایتون اوّلین پیام را برای او چاپ میکند. دومین پیام با ‘David’ و سومین مورد با ‘Carolina’ آغاز خواهد شد.
خروجی پیامی شخصیشده را برای هر شعبدهبازِ داخلِ لیست نشان میدهد:
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
همچنین میتوانید هر تعداد خط کد که خواستید در حلقهی for بنویسید. هر خط تورفته که بعد از خط for magician in magicians آمده، داخل حلقه تلقی شده و بر روی هر مقدارِ داخلِ لیست یک بار اجرا میشود. در نتیجه میتوانید هر آنچه میخواهید با هر کدام از اعضای لیست انجام دهید.
اکنون خط دومی را به پیام خود اضافه میکنیم تا به هر شعبدهباز بگوییم که منتظر شعبدهی بعدی او هستیم:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
از آنجایی که هر دو دستور print() را با تورفتگی نوشتهایم، هر دو خط برای هر شعبدهباز داخل لیست یک بار اجرا میشود. خط جدید (\n) در دومین دستور print() در خط ۲، پس از هر بار اجرای حلقه یک خط خالی اضافه میکند. این کار مجموعهای از پیامها را ایجاد میکند که با نظم برای هر عضو لیست گروهبندی شدهاند:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
David, that was a great trick!
I can't wait to see your next trick, David.
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
به حلقههای for خود هر تعداد خط که بخواهید میتوانید اضافه کنید. در عمل هنگام استفاده از حلقهی for عملیّات متفاوت و متعدّدی را با اعضای لیست انجام میدهید.
انجام دادن کاری پس از حلقهی for
درست پس از به پایان رسیدن یک حلقهی for چه اتفاقی میافتد؟ معمولاً میخواهید که خروجی به دست آمده را خلاصه کنید یا به کار دیگری بپردازید که برنامه باید انجام دهد.
خطهای کدی که بدون تورفتگی و پس از حلقهی for نوشته میشوند تنها یک بار و بدون تکرار اجرا میشوند. بیایید یک پیام تشکر کلّی برای تمام شعبدهبازان بنویسیم و از آنها برای اجرای عالیشان تشکر کنیم. برای قرار دادن این پیام پس از پیامهای جداگانهای که چاپ کردهایم، آن را بدون تورفتگی پس از حلقهی for مینویسیم:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
print("Thank you, everyone. That was a great magic show!")
چنانکه قبلاً دیدید دو دستور print() نخست، خطاب به هر شعبدهباز چاپ میشود. امّا چون خط ۳ تورفته نیست، تنها یک بار چاپ میشود:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
David, that was a great trick!
I can't wait to see your next trick, David.
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Thank you, everyone. That was a great magic show!
هنگام پردازش اطّلاعات با یک حلقهی for درمییابید که روش خوبی برای خلاصه کردنِ عملیّاتی است که روی کلِّ مجموعهی دادهها انجام میگیرد. برای مثال فرض کنید از یک حلقهی for برای آمادهسازی اوّلیّهی یک بازی استفاده میکنید به نحوی که با پردازش لیستی از شخصیتها، آن را یکی یکی روی صفحه نمایش میدهید. ممکن است پس از حلقه کدی اضافه کنید که پس از نمایشِ تمامِ شخصیتهای بازی دکمهی Play Now را نشان دهد.
4.3 - کارهای بیشتر در یک حلقهی for
پایتون از تورفتگی برای تعیین ارتباط یک خط یا گروهی از خطها با دیگر قسمتهای برنامه استفاده میکند. در مثالهای گذشته خطهایی که برای هر شعبدهباز پیامی جداگانه چاپ کرد جزئی از حلقهی for بودند زیرا آنها را با تورفتگی نوشته بودیم. بهکاررفتنِ تورفتگی در پایتون خواندنِ کد را بسیار ساده میکند و اساساً پایتون از فاصلهی خالی استفاده میکند تا شما را به نوشتن کدهای مرتّب با ساختار بصری واضح وادار کند. در برنامههای طولانیتر پایتون به قطعههای تورفتهی کد با سطحهای تورفتگیهای متفاوت برخواهید خورد. این سطوح تورفتگی به شما کمک میکند تا احساسی کلّی نسبت به سازمان و ساختار کلّی برنامه پیدا کنید.
هنگامی که شروع به نوشتن کدهای دارای تورفتگی میکنید باید مراقب تعداد اندک امّا متداولی خطای تورفتگی باشید. برای نمونه افراد گاهی خطهایی را که نیاز به تورفتگی ندارند، با تورفتگی مینویسند یا فراموش میکنند که خطهای نیازمندِ تورفتگی را اینچنین بنویسند. دیدن چند نمونه از این خطاها به شما کمک میکند که در آینده از آنها پرهیز کنید و اگر در برنامههایتان چنین مواردی رخ داد آنها را برطرف کنید.
اکنون برخی از معمولترین خطاهای تورفتگی را بررسی میکنیم.
فراموش کردن تورفتگی
همیشه خط بعد از حلقهی for را تورفته بنویسید. اگر فراموش کنید پایتون به شما تذکر میدهد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician)
فراخوانی print() در خط ۳ باید تورفته باشد امّا نیست. هنگامی که پایتون به دنبال خطی تورفته باشد امّا آن را نیابد، به شما میگوید که کدام خط چنین مشکلی دارد.
File "magicians.py", line 3
print(magician)
^
IndentationError: expected an indented block
این نوع از خطای تورفتگی را میتوان با تصحیح خط یا خطهایی که بلافاصله بعد از عبارت for آمدهاند برطرف کرد.
فراموش کردن تورفتگی خطهای اضافی
گاهی حلقهی شما بدون هیچ خطایی اجرا میشود امّا نتیجهی مطلوب را نمیگیرید. این گاهی به این دلیل رخ میدهد که میخواهید چند وظیفه را در یک حلقه انجام دهید امّا فراموش کردهاید که برخی از خطها را تورفته کنید.
برای مثال اگر فراموش کنید که دومین خط داخل حلقه را که به هر شعبدهباز میگوید که ما منتظر حقهی بعدی آنها هستیم، با تورفتگی بنویسید، به چنین خطایی برخواهید خورد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.")
فراخوانی print() در خط ۲ باید با تورفتگی همراه باشد، امّا چون پایتون حداقل یک خطِ تورفتهی درست بعد از حلقهی for میبیند، خطایی اعلام نمیکند. در نتیجه print() اول که خط آن تورفته است برای هر کدام از نامها یک بار اجرا میشود. دومین print() را بدون تورفتگی نوشتیم پس تنها یک بار بعد از پایان کار حلقه اجرا میشود. از آنجایی که آخرین مقداری که به magician نسبت داده شده ‘carolina’ است، او تنها کسی است که پیام ’looking forward to the next trick’ را دریافت میکند:
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
این یک خطای منطقی است. دستورِ برنامهی کدِ معتبرِ پایتون است امّا کد خروجی مطلوب را تولید نمیکند زیرا منطق آن مشکل دارد. اگر انتظار دارید که عمل مشخّصی روی تکتک اعضای لیست انجام شود امّا تنها یک بار این اتّفاق میافتد، باید یک یا چند خط برنامه را به سادگی تورفته کنید.
تورفتگیهای بیمورد
اگر به طور تصادفی خطی را تورفته کنید بدون این که نیاز باشد پایتون در مورد آن به شما اطّلاع خواهد داد:
File "hello_world.py", line 2
print(message)
^
IndentationError: unexpected indent
برای پرهیز از خطاهای ناخواستهی ناشی از تورفتگی، تنها زمانی این کار را انجام دهید که دلیل مشخّصی برای آن داشته باشید. در برنامههایی که در مقطع فعلی مینویسید تنها خطهایی باید تورفته باشند که به عملیّاتِ تکراری روی هر عضو در یک حلقهی for مربوط میشوند.
تورفتگی بیمورد پس از حلقه
اگر اتّفاقاً خطّی را که باید پس از پایان حلقه اجرا شود تورفته بنویسید، آن کد برای تمام اعضای لیست یک بار اجرا خواهد شد. گاهی این کار موجب میشود که پایتون خطایی گزارش کند امّا گاهی به خطای منطقی منجر میشود.
برای مثال اجازه دهید ببینیم با تورفته کردن خطی که حاوی پیام تشکر به همهی شعبدهبازها بود، چه اتّفاقی میافتد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
print("Thank you everyone, that was a great magic show!")
چون که خط ۴ تورفته است، یک بار برای هر فرد داخل لیست چاپ میشود، چنانکه اینجا میبینید:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
Thank you everyone, that was a great magic show!
David, that was a great trick!
I can't wait to see your next trick, David.
Thank you everyone, that was a great magic show!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Thank you everyone, that was a great magic show!
این هم یک خطای منطقی دیگر است مشابه با آنچه در «فراموش کردن تورفتگی خطهای اضافی» دیدید. از آنجایی که پایتون نمیداند شما میخواهید در برنامهی خود چه کاری انجام دهید، هر کدی را که به لحاظ نحوی معتبر باشد، اجرا میکند. اگر عملی چندین بار تکرار میشود درحالیکه تنها باید یک بار انجام میگرفت، احتمالاً باید کد مربوط آن را از تورفتگی خارج کنید.
فراموش کردن دونقطه
دونقطهی پایان عبارت for به پایتون میگوید که خط بعدی را به عنوان آغاز حلقه در نظر بگیرد.
magicians = ['alice', 'david', 'carolina']
for magician in magicians
print(magician)
اگر سهواً دونقطه را فراموش کنید، همانند خط ۲، به خطای نحوی برمیخورید چون پایتون نمیداند شما چه میخواهید بکنید. اگرچه رفع کردن این خطا ساده است، امّا پیدا کردن آن همیشه آسان نیست. مدتزمانی که یک برنامهنویس صرف یافتن خطاهای تککاراکتری مانند این میکند، شاید برای شما تعجّبآور باشد. چنین خطاهایی دیریاب هستند زیرا ما معمولاً تنها چیزی را میبینیم که انتظار دیدنش را داریم.
خودآزمایی
۴-۱- پیتزاها
به دستِ کم سه نوع پیتزای دلخواه خود فکر کنید. نام آنها را در لیستی ذخیره کنید و سپس به کمک حلقهی for نام هرکدام را چاپ کنید.
حلقه را به نحوی تغییر دهید که به جای نام هر پیتزا جملهای شامل نام آن چاپ شود. برای هر پیتزا باید جملهای شبیه به «I like pepperoni pizza» در خروجی داشته باشید.
خطی به پایان برنامهی خود، خارج از حلقهی for، اضافه کنید که بیان میکند شما چهقدر پیتزا دوست دارید. خروجی باید از سه یا چند خط در مورد انواع پیتزاهای مورد علاقهی شما تشکیل شده باشد و سپس جملهای مانند I really like pizza! اضافه شود.
۴-۲- حیوانات
حداقل سه حیوان مختلف با ویژگیهای مشترک در نظر بگیرید. نام آنها را در لیستی ذخیره کنید و به کمک یک حلقهی for نام هر یک را چاپ کنید.
برنامه را به نحوی تغییر دهید که جملهای دربارهی هر حیوان چاپ کند، مثلاً A dog would make a great pet.
خطّی به پایان برنامه اضافه کنید که ویژگی مشترک این حیوانات را بیان کند. مثلاً میتوانید جملهای مانند Any of these animals would make a great pet!.
4.4 - پرهیز از خطای تورفتگی
پایتون از تورفتگی برای تعیین ارتباط یک خط یا گروهی از خطها با دیگر قسمتهای برنامه استفاده میکند. در مثالهای گذشته خطهایی که برای هر شعبدهباز پیامی جداگانه چاپ میکرد جزئی از حلقهی for بودند زیرا آنها را با تورفتگی نوشته بودیم. بهکاررفتنِ تورفتگی در پایتون خواندنِ کد را بسیار ساده میکند و اساساً پایتون از فاصلهی خالی استفاده میکند تا شما را به نوشتن کدهای مرتّب با ساختار بصری واضح وادار کند. در برنامههای طولانیتر پایتون به قطعههای تورفتهی کد با سطحهای تورفتگیهای متفاوت برخواهید خورد. این سطوح تورفتگی به شما کمک میکند تا احساسی کلّی نسبت به سازمان و ساختار کلّی برنامه پیدا کنید.
هنگامی که شروع به نوشتن کدهای دارای تورفتگی میکنید باید مراقب تعداد اندک امّا متداولی خطای تورفتگی باشید. برای نمونه افراد گاهی خطهایی را که نیاز به تورفتگی ندارند، با تورفتگی مینویسند یا فراموش میکنند که خطهای نیازمندِ تورفتگی را اینچنین بنویسند. دیدن چند نمونه از این خطاها به شما کمک میکند که در آینده از آنها پرهیز کنید و اگر در برنامههایتان چنین مواردی رخ داد آنها را برطرف کنید.
اکنون برخی از معمولترین خطاهای تورفتگی را بررسی میکنیم.
فراموش کردن تورفتگی
همیشه خط بعد از حلقهی for را تورفته بنویسید. اگر فراموش کنید پایتون به شما تذکر میدهد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(magician)
فراخوانی print() در 1 باید تورفته باشد امّا نیست. هنگامی که پایتون به دنبال خطی تورفته باشد امّا آن را نیابد، به شما میگوید که کدام خط چنین مشکلی دارد.
File "magicians.py", line 3
print(magician)
^
IndentationError: expected an indented block
این نوع از خطای تورفتگی را میتوان با تصحیح خط یا خطهایی که بلافاصله بعد از عبارت for آمدهاند برطرف کرد.
فراموش کردن تورفتگی خطهای اضافی
گاهی حلقهی شما بدون هیچ خطایی اجرا میشود امّا نتیجهی مطلوب را نمیگیرید. این گاهی به این دلیل رخ میدهد که میخواهید چند وظیفه را در یک حلقه انجام دهید امّا فراموش کردهاید که برخی از خطها را تورفته کنید.
برای مثال اگر فراموش کنید که دومین خط داخل حلقه را که به هر شعبدهباز میگوید که ما منتظر حقهی بعدی آنها هستیم، با تورفتگی بنویسید، به چنین خطایی برخواهید خورد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
فراخوانی print() در 1 باید با تورفتگی همراه باشد، امّا چون پایتون حداقل یک خطِ تورفتهی درست بعد از حلقهی for میبیند، خطایی اعلام نمیکند. در نتیجه print() اول که خط آن تورفته است برای هر کدام از نامها یک بار اجرا میشود. دومین print() را بدون تورفتگی نوشتیم پس تنها یک بار بعد از پایان کار حلقه اجرا میشود. از آنجایی که آخرین مقداری که به magician نسبت داده شده ‘carolina’ است، او تنها کسی است که پیام ’looking forward to the next trick’ را دریافت میکند:
Alice, that was a great trick!
David, that was a great trick!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
این یک خطای منطقی است. دستورِ برنامهی کدِ معتبرِ پایتون است امّا کد خروجی مطلوب را تولید نمیکند زیرا منطق آن مشکل دارد. اگر انتظار دارید که عمل مشخّصی بر روی تکتک اعضای لیست انجام شود امّا تنها یک بار این اتّفاق میافتد، باید یک یا چند خط برنامه را به سادگی تورفته کنید.
تورفتگیهای بیمورد
اگر به طور تصادفی خطی را تورفته کنید بدون این که نیاز باشد پایتون در مورد آن به شما اطّلاع خواهد داد:
File "hello_world.py", line 2
print(message)
^
IndentationError: unexpected indent
برای پرهیز از خطاهای ناخواستهی ناشی از تورفتگی، تنها زمانی این کار را انجام دهید که دلیل مشخّصی برای آن داشته باشید. در برنامههایی که در مقطع فعلی مینویسید تنها خطهایی باید تورفته باشند که به عملیّاتِ تکراری روی هر عضو در یک حلقهی for مربوط میشوند.
تورفتگی بیمورد پس از حلقه
اگر اتّفاقاً خطّی را که باید پس از پایان حلقه اجرا شود تورفته بنویسید، آن کد برای تمام اعضای لیست یک بار اجرا خواهد شد. گاهی این کار موجب میشود که پایتون خطایی گزارش کند امّا گاهی به خطای منطقی منجر میشود.
برای مثال اجازه دهید ببینیم با تورفته کردن خطی که حاوی پیام تشکر به همهی شعبدهبازها بود، چه اتّفاقی میافتد:
magicians = ['alice', 'david', 'carolina']
for magician in magicians:
print(f"{magician.title()}, that was a great trick!")
print(f"I can't wait to see your next trick, {magician.title()}.\n")
print("Thank you everyone, that was a great magic show!")
چون که خط 1 تورفته است، یک بار برای هر فرد داخل لیست چاپ میشود، چنانکه اینجا میبینید:
Alice, that was a great trick!
I can't wait to see your next trick, Alice.
Thank you everyone, that was a great magic show!
David, that was a great trick!
I can't wait to see your next trick, David.
Thank you everyone, that was a great magic show!
Carolina, that was a great trick!
I can't wait to see your next trick, Carolina.
Thank you everyone, that was a great magic show!
این هم یک خطای منطقی دیگر است مشابه با آنچه در «فراموش کردن تورفتگی خطهای اضافی» دیدید. از آنجایی که پایتون نمیداند شما میخواهید در برنامهی خود چه کاری انجام دهید، هر کدی را که به لحاظ نحوی معتبر باشد، اجرا میکند. اگر عملی چندین بار تکرار میشود درحالیکه تنها باید یک بار انجام میگرفت، احتمالاً باید کد مربوط آن را از تورفتگی خارج کنید.
فراموش کردن دونقطه
دونقطهی پایان عبارت for به پایتون میگوید که خط بعدی را به عنوان آغاز حلقه در نظر بگیرد.
magicians = ['alice', 'david', 'carolina']
for magician in magicians
print(magician)
اگر سهواً دونقطه را فراموش کنید، همانند 1، به خطای نحوی برمیخورید چون پایتون نمیداند شما چه میخواهید بکنید. اگرچه رفع کردن این خطا ساده است، امّا پیدا کردن آن همیشه آسان نیست. مدتزمانی که یک برنامهنویس صرف یافتن خطاهای تککاراکتری مانند این میکند، شاید برای شما تعجّبآور باشد. چنین خطاهایی دیریاب هستند زیرا ما معمولاً تنها چیزی را میبینیم که انتظار دیدنش را داریم.
4.5 - ساختن لیستهای عددی
دلایل فراوانی برای ذخیره کردن مجموعهای از اعداد وجود دارد. مثلاً شما نیاز دارید موقعیتهای هر شخصیّت را در بازی ثبت کنید، همچنین شاید بخواهید بیشترین امتیاز بازیکن را هم ذخیره کنید. در بصریسازی داده تقریباً همیشه با مجموعهای از اعداد، مانند دماها، فاصلهها، جمعیتها، مقادیر طول و عرض جغرافیایی و دیگر انواع مجموعههای عددی سر و کار دارید.
لیستها برای ذخیره کردن مجموعههای اعداد ایدهآلاند و پایتون مجموعهای از ابزارها را برای کار کردن بهینه با لیستهای اعداد فراهم میآورد. زمانی که یاد گرفتید چگونه از این ابزارها به طور موثر استفاده کنید کد شما حتی برای لیستهایی شامل میلیونها عضو نیز به خوبی کار خواهد کرد.
بهکارگیری تابع range
دستور range() در پایتون ایجاد دنبالهای از اعداد را آسان میکند. برای مثال به کمک آن میتوانید دنبالهای از اعداد را مانند زیر چاپ کنید:
در این مثال range() تنها اعداد ۱ تا ۴ را چاپ میکند. این یکی دیگر از نتایج رفتار یکیجابهجا است که در زبانهای برنامهنویسی غالباً دیده میشود. تابع range() باعث میشود پایتون از اولین مقدار دادهشده شروع به شمارش کند و با رسیدن به دومین عددی که تعیین کردهاید توقف کند. از آنجایی که این تابع قبل از رسیدن به دومین مقدار مشخّصشده توقف میکند خروجی هرگز شامل مقدار پایانی نخواهد بود که در این مثال عدد ۵ است.
برای چاپ کردن اعداد ۱ تا ۵ از range(1,6) استفاده میکنید:
اگر هنگام استفاده از range() خروجی با آنچه انتظار دارید متفاوت است سعی کنید مقدار پایانی را یکی جابهجا کنید.
همچنین میتوانید به تابع range() تنها یک نشانوند بدهید آنگاه این تابع دنبالهی اعداد را از صفر آغاز میکند. برای مثال range(6) اعداد ۰ تا ۵ را برمیگرداند.
استفاده از range() برای تولید لیستی از اعداد
اگر بخواهید لیستی از اعداد تولید کنید میتوانید به کمک تابع list()، خروجیِ تابعِ range() را مستقیماً به لیست تبدیل کنید. زمانی که تابع range() را داخل list() قرار میدهید خروجی لیستی از اعداد خواهد بود.
در مثال بخش قبل به سادگی دنبالهای از اعداد را چاپ کردیم. میتوانیم از list() برای تبدیل این مجموعهی اعداد به لیست استفاده کنیم:
همچنین میتوانیم به کمک تابع range() از پایتون بخواهیم که برخی اعداد را در محدودهی دادهشده نادیده بگیرد. اگر ورودی سومی به range() بدهید، پایتون هنگام تولید اعداد آن مقدار را به عنوان طول گام در نظر میگیرد.
برای مثال اینجا نحوهی تولید اعداد زوج بین ۱ تا ۱۰ را میبینیم:
در این مثال تابع range() از مقدار ۲ شروع میکند و سپس ۲ تا به آن اضافه میکند. این تابع تا زمان رسیدن یا گذشتن از مقدار پایانی، اینجا ۱۱، اضافه کردن ۲ را ادامه میدهد و این خروجی را تولید میکند:
به کمک تابع range() میتوانید تقریباً هر نوع مجموعهی عددی را تولید کنید. برای مثال فرض کنید که میخواهید لیست اوّلین ۱۰ عددِ مربّع را که توانِ دوی اعداد ۱ تا ۱۰ است تولید کنید. در پایتون دو علامت ستاره (**) بیانگر توان است. اینجا روشی نشان داده شده که شما هم احتمالاً برای قرار دادن ۱۰ عدد مربّع در یک لیست استفاده خواهید کرد:
squares = []
for value in range(1, 11):
square = value **2 squares.append(square)
print(squares)
در 1 از یک لیست تهی شروع میکنیم. در 2 به پایتون میگوییم که به کمک تابع range() در اعداد ۱ تا ۱۰ حلقه بزند. در 3 مقدار فعلی به توان ۲ رسیده و در متغیّر square ذخیره میشود. در 4، مقدار جدید square به لیست squares افزوده میشود. در نهایت با پایان یافتن حلقه لیست اعداد مربّع در 5 چاپ میشود:
squares = []
for value in range(1,11):
squares.append(value**2)
print(squares)
کد در خط 1 همانند خطهای 3 و 4 فایل squares.py کار میکند. هر کدام از مقدارها در حلقه به توان ۲ رسیده و بلافاصله به لیستِ اعدادِ مربّع افزوده میشود.
برای ساختن لیستهایی که پیچیدهتر هستند میتوانید از هر دو روش استفاده کنید. گاهی استفاده از متغیّر موقّت خوانایی برنامه را بیشتر و گاهی کد را بیدلیل طولانی میکند. ابتدا بر روی کدی تمرکز کنید که میتوانید به روشنی آن را درک کنید و البته کاری را که میخواهید انجام میدهد. سپس همزمان با مرور کردن کد خود به دنبال رویکردهای بهینهتر بگردید.
محاسبات آماری ساده با لیستی از اعداد
تعدادی از دستورهای پایتون هنگام کار با لیستهای عددی سودمندند. برای مثال به راحتی میتوانید کمینه، بیشینه و مجموع یک لیست اعداد را به دست آورید:
در مثالهای این بخش از لیستهای کوچک اعداد بهره گرفته میشود تا به سادگی در صفحه جا شوند امّا اگر لیست شما شامل میلیونها عدد یا بیشتر نیز باشد برنامهها به همین ترتیب کار خواهند کرد.
تولید لیست به روش دربرداری
این رویکرد پیش از این برای تولیدِ لیستِ squares با سه یا چهار خط کد توضیح داده شد. روش دربرداری برای تولید لیست به شما امکان میدهد تا همین کار را تنها در یک خط انجام دهید. این روش حلقهی for و ایجاد عناصر جدید را در یک خط ترکیب و هر عضو جدید را به طور خودکار اضافه میکند. ارائه کردن این روش به برنامهنویسانِ تازهکار معمولاً متداول نیست امّا اینجا آورده میشود چون شما به زودی و به محض آغازِ بررسیِ کدهای دیگران به آن برخواهید خورد.
مثال زیر ساختن لیستی مشابه با لیست اعداد مربّع را که پیش از این دیدهاید نشان میدهد، امّا این بار با استفاده از روش دربرداری:
squares = [value**2for value in range(1, 11)]
print(squares)
برای استفاده از این دستور، از یک نامِ گویا برای لیست، مثل squares، شروع کنید. حال یک کروشه باز کنید و برای مقادیری که میخواهید در لیست ذخیره کنید، عبارتی تعریف کنید. در این مثال این عبارت value**2 است که مقدارها را به توان ۲ میرساند. در ادامه یک حلقهی for تعریف کنید تا اعدادِ ورودی به عبارتِ فوق را تولید کند. سرانجام کروشه را ببندید. حلقهی for در این مثال for value in range(1,11) است که مقادیر ۱ تا ۱۰ را به عبارت value**2 میخوراند. توجّه کنید که در پایان حلقهی for از دونقطه استفاده نشده است.
نتیجه مشابه همان لیستِ اعداد مربّعی است که پیشتر دیدید:
آشنایی با ساختن لیست با این روش کمی کار میبرد امّا هنگامی که برای ساختن لیستهای معمولی نیز آنها را راحت یافتید درمییابید که ارزش وقت گذاشتن را داشته است. هر گاه نوشتن سه یا چهار خط را برای ساختن لیستها تکراری و خستهکننده دیدید به فکر ساختن لیست به این روش بیفتید.
خودآزمایی
شمردن تا ۲۰
از یک حلقهی for برای شمردن از ۱ تا ۲۰، و خودِ ۲۰، استفاده کنید.
یک میلیون
لیستی از اعدادِ ۱ تا یک میلیون بسازید سپس به کمک حلقهی for آن را چاپ کنید. (اگر نمایش خروجی طولانی شد آن را با فشردن Ctrl+C یا بستن پنجرهی خروجی متوقّف کنید.)
جمع کردن یک میلیون عدد
لیستی از اعداد ۱ تا یک میلیون بسازید سپس با کمک min() و max() مطمئن شوید که لیست واقعاً از ۱ شروع و به یک میلیون ختم میشود. سپس از sum() استفاده کنید تا دریابید پایتون چهقدر سریع میتوان یک میلیون عدد را با هم جمع کند.
اعداد فرد
به کمک ورودی یا نشانوند سوم تابع range() لیستی از اعداد فرد بین ۱ تا ۲۰ درست کنید. سپس به کمک یک حلقهی for آن را چاپ کنید.
مضارب ۳
لیستی از مضربهای ۳ بین ۳ و ۳۰ بسازید و با یک حلقهی for اعداد داخل آن را چاپ کنید.
اعداد مکعّب
به عددی که به توان ۳ رسیده باشد مکعّب گویند. برای مثال مکعّب ۲ در پایتون با 3**2 نمایش داده میشود. لیستی از ۱۰ عدد مکعّب نخست (که مکعّب اعداد صحیح از ۱ تا ۱۰ است)، بسازید و با یک حلقهی for این اعداد را نمایش دهید.
اعداد مکعّب به روش دربرداری
به کمکِ این روش لیستی از ۱۰ عدد مکعّب نخست تولید کنید.
4.6 - کار کردن با بخشی از یک لیست
در فصل ۳ روش دسترسی به تک تک اعضای یک لیست و در این فصل کار کردن با تمام اعضای آن را آموختید. در عین حال شما میتوانید با گروه مشخّصی از اعضای یک لیست کار کنید که پایتون آن را برش مینامد.
ساختن برشی از لیست
برای برش دادن یک لیست شاخص آغازی و پایانی اعضای مورد نظر خود را مشخّص میکنید. همانند تابع range() پایتون پیش از رسیدن به آخرین عضوِ مشخّص شده توقّف میکند. برای بریدن سه عضو اوّل یک لیست باید شاخصهای ۰ تا ۳ را درخواست کنید که اعضای ۰، ۱ و ۲ را برمیگرداند.
میتوانید هر زیرمجموعهای را از یک لیست تولید کنید. مثلاً اگر برشی از لیست را شامل اعضای دوم، سوم و چهارم بخواهید میتوانید آن را از شاخص ۱ آغاز و به شاخص ۴ ختم کنید:
از همین ترکیب میتوانید برای برشهای انتهایی لیست استفاده کنید. برای نمونه اگر برشی شامل همهی اعضا از شاخص سوم تا پایان لیست را بخواهید میتوانید شاخص شروع را ۲ گذاشته و شاخص پایان را حذف کنید:
به این ترتیب میتوانید صرف نظر از طول لیست، تمام اعضای بین یک شاخص خاص و پایان آن را در خروجی داشته باشید. به خاطر بیاورید که شاخص منفی میتوانست عضوی را در فاصلهی مشخّصی از پایان لیست برگرداند. پس به کمک آن میتوانید برشهایی از انتهای لیست تولید کنید. برای مثال اگر بخواهیم سه بازیکنِ آخرِ ترکیبِ تیم را برگردانیم از برش players[-3:] استفاده میکنیم:
این مثال نام سه بازیکن آخر را چاپ میکند و با افزایش تعداد بازیکنانِ لیست هم بدون مشکل کار خواهد کرد.
نکته
هنگام تعریف برش میتوانید عدد سومی هم داخل کروشه بنویسید. این عدد مشخّص میکند که پایتون چند عضو را در بازهی مشخّصشده نادیده بگیرد. (مانند آنچه که در تابع range بود.)
حلقه زدن در یک برش
اگر بخواهید تنها در زیرمجموعهای از اعضای یک لیست حلقه بزنید میتوانید از یک برش در حلقهی for استفاده کنید. در مثال زیر در سه بازیکن اوّل حلقه میزنیم و نام آنها را به عنوان یک ترکیبِ تیم ساده چاپ میکنیم:
players = ['charles', 'martina', 'michael', 'florence', 'eli']
print("Here are the first three players on my team:")
for player in players[:3]:
print(player.title())
پایتون در 1 به جای حلقه زدن در کل لیست، تنها در سه نام اول میچرخد:
Here are the first three players on my team:
Charles
Martina
Michael
برشها در موارد خاصّی بسیار کاربردیاند. برای مثال زمانی که یک بازی میسازید، هر بار که بازیکن بازی را به پایان رساند میتوانید امتیاز نهایی او را به لیستی بیفرایید. سپس با مرتّب کردن لیست به صورت نزولی و برداشتن برشی از سه امتیاز اول، میتوانید سه امتیاز بالای بازیکن را به دست آورید. زمانی که با دادهها کار میکنید میتوانید از برشها برای پردازش دادهها در تکّهها با اندازهی مشخّص استفاده کنید. یا هنگام ساختن یک برنامهی تحت وب از برشها برای نمایش اطّلاعات در دستهای از صفحات استفاده کنید به طوریکه در هر صفحه مقدار مشخّصی از اطّلاعات قرار گیرد.
کپی کردن یک لیست
بسیاری از اوقات شما میخواهید بر پایهی یک لیست موجود، لیستی کاملاً جدید ایجاد کنید. بیایید چگونه کپی کردنِ یک لیست را بررسی و وضعیتی نمونه را امتحان کنیم که کپی کردن لیست در آن مفید است.
برای کپی کردن یک لیست میتوانید با حذف کردن شاخصهای ابتدایی و پایانی ([:]) برشی از آن را تهیه کنید که شامل تمام اعضای لیست اوّلّیه است. این کار به پایتون میگوید که برشی از لیست تهیه کند که از عضو اول شروع و به عضو آخر ختم میشود و باعث ایجاد یک کپی از کل لیست میشود.
برای مثال فرض کنید که لیستی از غذاهای مورد علاقهمان موجود است و میخواهیم لیستی جداگانه و مشابه برای یک دوست درست کنیم. دوستمان تمام غذاهای مورد علاقهی ما را دوست دارد در نتیجه میتوانیم لیست او را با کپی کردن لیست خود درست کنیم:
در 1 لیستی از غذاهایی را که دوست داریم به نام my_foods درست کردهایم. در 2 لیست جدیدی به نام friend_foods میسازیم. با درخواست برشی از my_foods بدون مشخّص کردن هیچ شاخصی و ذخیره کردن آن در friend_foods، یک کپی از my_foods ایجاد میکنیم. اگر هر دو لیست را چاپ کنیم میبینیم که هر دو دربرگیرندهی غذاهای مشابهی هستند:
My favorite foods are:
['pizza', 'falafel', 'carrot cake']
My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake']
برای این که ثابت کنیم واقعاً دو لیست جداگانه داریم یک غذای جدید به هر یک از لیستها اضافه میکنیم تا نشان دهیم که هر لیست غذاهای مورد علاقهی یک نفر را به صورت جداگانه نگه میدارد:
در 1 همانند قبل، اعضای اوّلیّهی داخل my_foods را در لیست جدید friend_foods کپی کردیم. در ادامه یک غذای جدید به هر لیست اضافه میکنیم: در 2 ‘cannoli’ را به my_foods و در 3 ‘ice cream’ را به friend_foods افزودیم. سپس هر دو لیست را چاپ میکنیم تا ببینیم هر کدام از این غذاها به لیست مناسب اضافه شدهاند یا نه:
My favorite foods are:
['pizza', 'falafel', 'carrot cake', 'cannoli']
My friend's favorite foods are:
['pizza', 'falafel', 'carrot cake', 'ice cream']
خروجی نشان میدهد که ‘cannoli’ اکنون در لیست غذاهای مورد علاقهی ما وجود دارد امّا ‘ice cream’ اینچنین نیست. در 5 میبینیم که ‘ice cream’ در لیست دوستمان هست امّا ‘cannoli’ نیست. اگر صرفاً و به سادگی متغیّر friend_foods را برابر با my_foods قرار میدادیم، منجر به ایجاد دو لیست جداگانه نمیشد. برای مثال اینجا میبینیم اگر لیستی را بدون استفاده از برش کپی کنیم چه اتّفاقی میافتد:
در 1 به جای کپی کردن my_foods در friend_foods، صرفاً این دو متغیّر را برابر با یکدیگر قرار دادیم. این دستور به پایتون میگوید که متغیّر جدید friend_foods را به لیستی ارتباط دهد که خود قبلاً با my_foods مرتبط بوده است. بنابراین الان هر دو متغیّر به یک لیست مشابه اشاره میکنند. در نتیجه اگر ‘cannoli’ را به my_foods اضافه کنید این عضو در friend_foods نیز ظاهر خواهد شد. به طور مشابه اگر ‘ice cream’ را فقط به friend_foods اضافه کنید در هر دو لیست دیده خواهد شد.
خروجی نشان میدهد که هر دو لیست یکسان هستند و این چیزی نیست که ما میخواستیم:
الان نگران جزئیات این مثال نباشید. اساساً هر جا هنگام کپی کردنِ یک لیست رفتاری غیرمنتظره دیدید مطمئن شوید که همانند مثال اوّل، از یک برش استفاده کردهاید.
خودآزمایی
برشها
به یکی از برنامههایی که در این فصل نوشتهاید چند خط اضافه کنید تا کارهای زیر را انجام دهد:
پیام The first three items in the list are: و سپس به کمک یک برش سه عضو اوّل لیست برنامه را چاپ کند.
پیام Three items from the middle of the list are: و سپس به کمک یک برش سه عضو میانی لیست برنامه را چاپ کند.
پیام The last three items in the list are: و سپس به کمک یک برش سه عضو آخر لیست برنامه را چاپ کند.
پیتزاهای من، پیتزاهای تو
با شروع از برنامهی تمرین ۴-۱ یک کپی از لیست پیتزاها ایجاد کنید و آن را friend_pizzas بنامید. سپس این کارها را انجام دهید:
یک پیتزای جدید به لیست اصلی اضافه کنید. پیام My favorite pizzas are: و در ادامه به کمک یک حلقهی for لیست اول را چاپ کنید. پیام My friend's pizzas are: و با یک حلقهی for لیست دوم را چاپ کنید. مطمئن شوید که هر پیتزا در لیست مناسب ذخیره شده باشد.
حلقههای بیشتر
در تمام نسخههای foods.py برای صرفهجویی در فضا از بهکارگیری حلقهی for برای چاپ لیست پرهیز شده است. یکی از نسخهها را انتخاب کنید و دو حلقهی for برای چاپ کردن هر کدام از لیستها به آن بیفزایید.
4.7 - چندتاییها
لیستها برای ذخیرهسازی مجموعهای از اعضا مناسب است که ممکن است در طول برنامه تغییر کنند. قابلیّت تغییر دادن لیستها به ویژه زمانی مهم است که بخواهید با فهرستی از کاربران یک وبسایت یا شخصیتهای یک بازی کار کنید. با این حال گاهی به ساختن لیستی نیاز دارید که نباید تغییر کند. چندتاییها چنین امکانی به شما میدهند. پایتون به مقادیری که مقدارشان قابل تغییر نیست تغییرناپذیر میگوید و لیستِ تغییرناپذیرْ چندتایی نامیده میشود.
تعریف کردن یک چندتایی
چندتایی درست مشابه یک لیست است با این تفاوت که برای تعریف آن به جای کروشه از پرانتز استفاده میشود. پس از تعریف چندتایی میتوانید درست همانند یک لیست، با استفاده از شاخصِ هر عضو به مقدار آن دسترسی داشته باشید.
برای مثال اگر مستطیلی داشته باشیم که اندازهی آن همیشه باید ثابت باشد، برای اطمینان از تغییر نیافتن اندازهها میتوانیم ابعاد آن را در یک چندتایی قرار دهیم:
چندتایی dimensions را با استفاده از پرانتز به جای کروشه در 1 تعریف کردهایم. در 2 به کمک همان روشی که برای دسترسی به اعضای لیست استفاده میکردیم، هر عضو چندتایی را جداگانه چاپ میکنیم:
کد در 1 سعی میکند مقدار اولّین بُعد را تغییر دهد امّا پایتون خطایی مربوط به نوع برمیگرداند. دلیل این مساله این است که سعی کردهایم یک چندتایی را تغییر دهیم و این کار برای این نوع مجاز نیست، پایتون میگوید که نمیتوانیم مقدار جدیدی به یکی از اعضای داخل چندتایی نسبت دهیم:
Traceback (most recent call last):
File "dimensions.py", line 2, in <module>
dimensions[0] = 250
TypeError: 'tuple' object does not support item assignment
این کار به نفع ماست چون انتظار داریم هر گاه خطی در برنامه تلاش کرد ابعاد مستطیل را عوض کند، پایتون اعلام خطا کند.
نکته
از لحاظ فنّی چندتاییها با وجود کاما معنا مییابند؛ پرانتزها آنها را مرتّبتر و خواناتر میکنند. اگر بخواهید یک چندتایی با تنها یک عضو ایجاد کنید باید کامایی در ادامهی آن عضو بنویسید:
اگرچه نمیتوانید محتویات یک چندتایی را تغییر دهید امّا میتوانید به متغیّری که بیانگر یک چندتایی است، مقدار جدیدی نسبت دهید. بنابراین اگر به تغییر دادن ابعاد نیاز داشتیم میتوانیم کل چندتایی را دوباره تعریف کنیم:
dimensions = (200, 50)
print("Original dimensions:")
for dimension in dimensions:
print(dimension)
dimensions = (400, 100)
print("\nModified dimensions:")
for dimension in dimensions:
print(dimension)
خطهایی که از 1 شروع میشوند چندتاییِ اوّلیّه را تعریف و آن را چاپ میکنند. در 2 یک چندتاییِ جدید به متغیّر dimensions نسبت میدهیم. در 3 ابعاد جدید را چاپ میکنیم. پایتون این بار خطایی اعلام نمیکند زیرا بازنشانی یک متغیّر مجاز است:
Original dimensions:
200
50
Modified dimensions:
400
100
چندتاییها در مقایسه با لیستها ساختمانهای دادهی سادهای هستند. هنگامی از آنها استفاده کنید که مجموعهی مقادیرِ موردِ نظرِ شما در طولِ عمرِ برنامه تغییر نمیکنند.
خودآزمایی
بوفه
یک رستورانِ بوفهای تنها پنج غذای اصلی ارائه میکند. پنج غذای ساده را در نظر بگیرید و نام آنها را در یک چندتایی ذخیره کنید.
با یک حلقهی for غذاهایی را که رستوران عرضه میکند چاپ کنید.
سعی کنید یکی از مقادیر چندتایی را تغییر دهید و مطمئن شوید پایتون ایجاد تغییرات را رد میکند.
رستوران فهرست غذاها را تغییر میدهد و دو غذا را جایگزین میکند. یک خط برای بازنویسی چندتایی اضافه کنید و سپس به کمک حلقهی for تمام اعضای فهرستِ بازنگریشده را چاپ کنید.
4.8 - شکلدهی کدها
اکنون که کدهای طولانیتری مینویسید دانستن مواردی دربارهی نحوهی شکل دادن به کدها ارزشمند است. زمانی صرف کنید تا کدهایتان را تا جای ممکن قابل خواندن کنید. نوشتن کدِ خوانا به شما کمک میکند تا کاری را که برنامه انجام میدهد ردیابی کنید و همچنین دیگران کد شما راحتتر بفهمند.
برنامهنویسان پایتون بر روی تعدادی از قراردادها و شیوهنامهها توافق کردهاند تا مطمئن شوند که ساختارِ کدِ هر کس تقریباً به یک شکل است. به محض این که نوشتنِ کدِ تمیزِ پایتون را فراگرفتید، قادر خواهید بود که ساختار کلی برنامهی پایتون هر کس را درک کنید، به شرطی که از یک راهنمای مشترک پیروی کنید. اگر امیدوارید که روزی به یک برنامهنویس حرفهای تبدیل شوید، باید هر چه زودتر دنبال کردنِ این راهنماها را آغاز کنید تا تبدیل به عادتهای خوبی شوند.
شیوهنامه
هرگاه کسی بخواهد تغییری در زبان پایتون ایجاد کند یک پیشنهادِ پیشرفتِ پایتون (PEP) مینویسد. یکی از قدیمیترین PEPها PEP 8 است که به برنامهنویسان پایتون یاد میدهد به چه شیوهای کد خود را شکل دهند. PEP 8 نسبتاً طولانی است ولی بیشتر آن مربوط به کدهایی است که از آنچه شما تا الان دیدهاید پیچیدهترند.
شیوهنامهی پایتون بر اساس این تفکر نوشته شده است که کد بیشتر از آنکه نوشته شود خوانده میشود. شما کد را یک بار مینویسید و سپس هنگام اشکالزدایی خواندن آن را شروع میکنید. هنگام افزودنِ یک ویژگیِ جدید به برنامهی خود زمان بیشتری صرف خواندن کد میکنید. هنگامی که کد خود را با دیگر برنامهنویسان به اشتراک میگذارید آنها نیز کد شما را خواهند خواند.
اگر بین نوشتن کدی که راحتتر نوشته شود و کدی که راحتتر خوانده شود حقّ انتخاب داشته باشید، برنامهنویسان پایتون تقریباً همیشه شما را به انتخاب گزینهی دوم تشویق میکنند. راهنماهای زیر به شما کمک میکنند که از ابتدا کدی شفافتر و واضحتر بنویسید.
تورفتگی
PEP 8 به شما توصیه میکند برای ایجاد تورفتگی از ۴ فاصله استفاده کنید. استفاده از ۴ فاصله خوانایی برنامه را افزایش میدهد و در عین حال امکان ایجاد تورفتگیهای بیشتر را در هر خط ایجاد میکند.
در سندِ یک واژهپرداز معمولاً برای ایجاد تورفتگی به جای فاصله از تب استفاده میشود. این کار برای اسناد یک واژهپرداز مناسب است امّا هنگامی که تب و فاصله را با یکدیگر مخلوط کنید مفسّر پایتون گیج میشود. هر ویرایشگر متنی تنظیماتی در اختیار شما میگذارد تا بتوانید از دکمهی TAB استفاده کنید امّا در نهایت آن تبها را به تعدادی فاصله تبدیل کند. شما قطعاً میتوانید از دکمهی TAB استفاده کنید امّا مطمئن شوید که ویرایشگر شما حتماً در فایل شما به جای تب، فاصله درج میکند.
ترکیب کردن تب و فاصله در یک فایل ممکن است موجب خطاهایی شود که تشخیص آن بسیار مشکل است. اگر فکر میکنید در فایلی ترکیبی از این دو وجود دارد، در بیشتر ویرایشگرها امکان تبدیل تمام تبها به فاصله وجود دارد.
طول خط
بسیاری از برنامهنویسان پایتون پیشنهاد میکنند که طول هر خط کمتر از ۸۰ حرف باشد. از لحاظ تاریخی این توصیه به این دلیل ایجاد شده که بیشتر رایانهها تنها ۷۹ کاراکتر در هر خط ترمینال جا میدادند. در حال حاضر میتوان حرفهای بسیار بیشتری در یک خط نمایشگر جا داد امّا دلایل دیگری برای وفادار ماندن به استاندارد خط ۷۹حرفی وجود دارد. برنامهنویسان حرفهای گاهی چند فایل را همزمان روی صفحهی خود باز میکنند و طول استاندارد خط به آنها اجازه میدهد که در دو یا سه فایلی که کنار هم هستند، تمام خطها را ببینند. PEP 8 همچنین توصیه میکند طول هر خطِ توضیحی را حداکثر ۷۲ حرف بگیرید زیرا ابزارهایی که برای پروژههای بزرگتر به طور خودکار مستندات را تولید میکنند در ابتدای هر توضیح چند حرف دیگر اضافه میکنند.
راهنماییهای PEP 8 در مورد طول خط وحیِ مُنزَل نیست و برخی از گروهها حد ۹۹حرفی را میپسندند. در زمان یادگیری لازم نیست خیلی نگران طول خط باشید امّا حواستان باشد که افرادی که به صورت مشارکتی کار میکنند معمولاً همیشه از راهنماهای PEP 8 پیروی میکنند. بیشترِ ویرایشگرها برای شما یک سرنخ بصری ایجاد میکنند که معمولاً یک خط عمودی روی صفحه است و این حدود را به شما نشان میدهند.
نکته
پیوست ب به شما نشان میدهد که چگونه ویرایشگر متنی خود را برای وارد کردن همیشگی فاصله به جای تب و نمایش یک خط عمودی راهنما برای کمک به پیروی از حد ۷۹ حرفی تنظیم کنید.
خطهای خالی
برای گروهبندی بصری بخشهای مختلف برنامه از خطهای خالی استفاده کنید. بهتر است از این خطها برای شکلدهی به فایلهای خود استفاده کنید امّا زیادهروی نکنید. با دنبال کردن مثالهای این کتاب به یک نسبت درست خواهید رسید. برای مثال اگر برای ایجاد لیستی چهار یا پنج خط کد نوشتهاید و بعد از آن سه خط دیگر برای ایجاد لیست دیگری نوشتهاید بهتر است بین این دو بخش یک خط خالی فاصله بیندازید. با این حال نباید بیش از یک خط خالی، مثلاً چهار یا پنج خط، فاصله ایجاد کنید.
خطهای خالی شیوهی اجرای برنامهی شما را تغییر نمیدهند امّا بر خوانایی برنامه تاثیر میگذارند. مفسّر پایتون از تورفتگی افقی برای تشخیص معنای برنامهی شما استفاده میکند امّا به فاصلهگذاری عمودی اهمّیّتی نمیدهد.
دیگر شیوهنامهها
PEP 8 توصیههای متعدّد دیگر هم برای شکلدهی دارد امّا بیشتر آنها در برنامههایی کاربرد دارند که پیچیدهتر از برنامههای فعلی شما هستند. همزمان با آموختن ساختارهای پیچیدهتر پایتون بخشهای مربوط در راهنماهای PEP 8 را با شما به اشتراک خواهم گذاشت.
خودآزمایی
PEP 8
به شیوهنامهی اصلی PEP 8 در آدرس https://python.org/dev/peps/pep-0008/ نگاهی بیندازید. از بیشترِ آن استفاده نخواهید کرد امّا شاید مرور آن جالب باشد.
مرور کد
سه نمونه از برنامههایی را که در این فصل نوشتهاید انتخاب کنید و آنها را برای تطبیق با PEP 8تغییر دهید:
از چهار فاصله برای هر سطح تورفتگی استفاده کنید. ویرایشگر متنی خود را تنظیم کنید تا با هر بار فشردن دکمهی TAB چهار فاصله وارد کند. (برای مشاهدهی دستورالعمل انجام این کار پیوست ب را ببینید.)
کمتر از ۸۰ حرف در هر خط استفاده کنید و ویرایشگر خود را برای نمایش یک راهنمای عمودی در موقعیّت ۸۰امین حرف تنظیم کنید.
در برنامههای خود بیش از اندازه از خط خالی استفاده نکنید.
4.9 - خلاصه
در این فصل آموختید که چگونه با اعضای یک لیست به طور موثر کار کنید. آموختید که چگونه با یک حلقهی for با اعضای داخل یک لیست کار کنید، پایتون چگونه از تورفتگی برای ساختار دادن به برنامه استفاده میکند و چگونه از برخی خطاهای تورفتگی پرهیز کنیم. یاد گرفتید که چگونه لیستهای عددی تولید کنید و با چند عملیّات ساده آشنا شدید که میتوان بر روی آنها اعمال کرد. آموختید که چگونه برای کار کردن با زیرمجموعهای از لیست، آن را برش دهیم و چگونه به این روش یک لیست را کپی کنیم. دربارهی چندتاییها آموختید که درجهای از محافظت برای مقادیری که نباید تغییر کنند ایجاد میکنند. همچنین آموختید برای سادهتر کردن خواندن یک کد، چگونه به کدی که به تدریج پیچیدهتر میشود شکل بدهیم.
در فصل پنجم میآموزید که چهطور به کمک عبارتهای if به شرایط مختلف واکنش مناسب نشان دهید. یاد میگیرید که چگونه مجموعهای نسبتاً پیچیدهای از آزمونهای شرطی را به هم پیوند دهید تا دقیقاً به وضعیّت یا اطّلاعاتی که میخواهید پاسخ مناسب دهید. همچنین میآموزید که از عبارتهای if هنگام حلقه زدن در یک لیست استفاده کنید تا بر روی برخی از اعضای مشخّص آن کار خاصّی انجام دهید.
5 - گزارههای شرطی
5.1 - یک مثال ساده
برنامهنویسی گاهی مستلزم آزمودن مجموعهای از شرطها و تصمیمگیری در مورد عملی است که باید بر اساس آن شرطها اتّخاذ شود. گزارههای if در پایتون این امکان را فراهم میآورند که حالتِ فعلی برنامه را بررسی کنیم و به آن پاسخی متناسب دهیم.
در این فصل نوشتن آزمونهای شرطی را برای بررسی هر نوع شرط میآموزید. نوشتن گزارههای سادهی if و همچنین ساختن دنبالهای پیچیده از گزارههای if را فراخواهید گرفت تا تشخیص دهید که شرایطی که به طور دقیق تعیین کردهاید چهزمانی برآورده میشوند. سپس همین مفهوم را به لیستها تعمیم میدهید و قادر خواهید بود حلقهی forای بنویسید که بیشتر اعضای یک لیست را به یک روش و تعداد مشخّص دیگری را به روشی دیگر مدیریّت کند.
یک مثال ساده
مثال کوتاهی که در ادامه میآید نشان میدهد که آزمونهای if چگونه به شما امکان میدهند که به شرایط خاصّی به درستی پاسخ دهید. تصوّر کنید که لیستی از خودروها دارید و میخواهید اسامی آنها را چاپ کنید. نام خودروها بیشتر نام خاص هستند پس باید آنها را در حالت عنوان (با حروف اوّل بزرگ) چاپ کرد. با این حال مقدار ‘bmw’ باید تماماً با حروف بزرگ چاپ شوند. کد زیر در لیست خودروها حلقه میزند و به دنبال مقدار ‘bmw’ میگردد. هر گاه به این مقدار رسید آن را به جای حالت عنوان، با حروف بزرگ چاپ میکند:
cars = ['audi', 'bmw', 'subaru', 'toyota']
for car in cars:
if car =='bmw':
print(car.upper())
else:
print(car.title())
حلقهی این مثال در 1 ابتدا بررسی میکند که آیا نام فعلی ‘car’ی ‘bmw’ است یا نه؟ اگر چنین باشد این نام را با حروف بزرگ چاپ میکند. اگر مقدار car چیزی جز این بود، آن را در حالت عنوان چاپ میکند:
این مثال برخی از مفاهیمی را که شما در این فصل میآموزید، در هم آمیخته است. اکنون به انواع آزمونهایی نگاه میاندازیم که میتوانید از آنها برای آزمودنِ شرطها در برنامهی خود استفاده کنید.
5.2 - آزمونهای شرطی
قلب هر گزارهی if عبارتی است که با True و False (درست و نادرست) بودن سنجیده میشود و آن را آزمون شرطی مینامیم. پایتون از مقدارهای True و False برای تصمیمگیری در مورد اجرا شدن کد داخل گزارهی if استفاده میکند. اگر یک آزمون شرطی True (درست) ارزیابی شود، پایتون کدی را که بعد از گزارهی if آمده اجرا میکند. اگر آزمون False (نادرست) باشد پایتون کدی را که در ادامهی گزارهی if آمده نادیده میانگارد.
بررسیِ تساوی
بیشتر آزمونهای شرطی مقدارِ کنونیِ یک متغیّر را با یک مقدار مشخّصِ موردِ نظر مقایسه میکنند. سادهترین آزمون شرطیْ تساویِ مقدارِ یک متغیّر را با یک مقدار دلخواه بررسی میکند:
خط ۱ مقدار car را به کمک یک علامت تساوی برابر با ‘bmw’ قرار میدهد، چنانکه پیش از این در موارد بسیاری آن را دیدهاید. خط ۲ به کمک دو علامت تساوی (==) بررسی میکند آیا مقدار car برابر با ‘bmw’ است یا نه؟ عملگر برابری در صورت یکی بودن مقادیرِ دو طرف خود، True و در غیر این صورت False برمیگرداند. مقادیرِ این مثال به هم میخورند پس پایتون True برمیگرداند.
اگر مقدار car چیزی جز ‘bmw’ باشد، این آزمون False برمیگرداند:
علامت تساوی تکّی در واقع یک حکم است، میتوانید کد ۱ را به این صورت بخوانید: «مقدار car را برابر با ‘audi’ قرار بده.» از طرف دیگر علامت تساوی دوتایی، مانند ۲، پرسشی مطرح میکند: «آیا مقدار car برابر با ‘bmw’ است؟» بیشترِ زبانهای برنامهنویسی از علامتهای تساوی به همین شکل استفاده میکنند.
نادیده گرفتن بزرگ و کوچکی حروف هنگام بررسی تساوی
آزمودن تساوی در پایتون به بزرگی و کوچکی حروف حسّاس است. برای مثال دو کلمه با حروف یکسان امّا بزرگ و کوچکیِ متفاوت، یکسان تلقّی نمیشوند:
اگر بزرگی و کوچکی حروف مهم باشد این روش مفید است. امّا اگر این مسأله اهمّیّتی نداشته باشد و به جای آن میخواهید تنها مقدار یک متغیّر را بررسی کنید، میتوانید مقدار آن را پیش از مقایسه به حروف کوچک تبدیل کنید:
این آزمون باید صرف از شکل ‘Audi’ هم True برگرداند زیرا اکنون دیگر به بزرگ و کوچکی حروف حسّاس نیست. تابع lower() مقدار اوّلیّهی ذخیرهشده در car را تغییر نمیدهد، پس میتوانید این گونه مقایسهها را بدون ایجاد تغییر در مقدار متغیّر انجام دهید:
>>> car ='Audi'>>> car.lower()=='audi'True
>>> car
'Audi'
وبسایتها قوانین مشخّصی مانند این را بر دادههایِ ورودیِ کاربران اعمال میکنند. برای مثال سایتی ممکن است از یک آزمون شرطی مانند این استفاده کند تا مطمئن شود کاربر یک نام کاربری واقعاً یکتا دارد، نه صرفاً نام کاربری کس دیگری با تغییری در بزرگی و کوچکی حروف. زمانی که کسی نام کاربری جدیدی را ثبت میکند مقدار آن به حروف کوچک تبدیل میشود و سپس با تمام نامهای کاربری موجودِ تبدیلشده به حروف کوچک، مقایسه میشود. طی چنین آزمونی اگر هر ترکیبی از ‘john’ قبلاً ثبت شده باشد، نام کاربریای مانند ‘John’ رد خواهد شد.
بررسی نابرابری
اگر بخواهید نابرابری دو مقدار را بررسی کنید، میتوانید از ترکیب یک علامت تعجّب و علامت تساوی (!=) استفاده کنید. علامت تعجّب، مانند بسیاری از زبانهای برنامهنویسی دیگر، بیانگر «نیست» است.
اکنون از یک عبارت if دیگر برای بررسی کاربرد عملگر نابرابری استفاده میکنیم. مواد درخواستی برای روی پیتزا را در متغیّری ذخیره میکنیم و اگر ماهی کولی سفارش نداده باشد، پیامی خطاب به او صادر میکنیم:
requested_topping ='mushrooms'if requested_topping !='anchovies':
print("Hold the anchovies!")
خطّ ۱ مقدار requested_topping را با مقدار ‘anchovies’ مقایسه میکند. اگر دو مقدار برابر نباشند پایتون True برمیگرداند درنتیجه کدی که در ادامهی گزارهی if آمده اجرا میکند. اگر دو مقدار برابر باشند پایتون False برمیگرداند و آن خط را اجرا نخواهد کرد.
چون مقدار requested_topping برابر ‘anchovies’ نیست، تابع print() اجرا میشود:
>>> age =19>>> age < 21True
>>> age <=21True
>>> age > 21False
>>> age >=21False
هر مقایسهی ریاضی میتواند به عنوان بخشی از یک عبارت if به کار گرفته شود که به شما برای تشخیص شرطهای دقیق موردنظرتان کمک میکند.
5.3 - بررسی شرطهای چندگانه
شاید بخواهید چند شرط را همزمان بررسی کنید. برای نمونه گاهی برای انجام عملی به True بودن همزمان دو شرط نیاز دارید. زمانی دیگر ممکن است ارضا شدن تنها یکی از دو شرط کافی باشد. دو کلیدواژهی and و or میتواند شما را در این شرایط یاری کند.
بهکارگیری and برای بررسی شرطهای چندگانه
برای بررسی درست (True) بودنِ همزمانِ دو شرط از کلیدواژهی and برای ترکیب دو آزمون شرطی استفاده کنید. اگر هر دو آزمون موفق باشند عبارت کلّی درست (True) خواهد بود. در صورتی که یکی یا هر دوی آزمونها ناموفّق باشند، عبارت کلی نیز نادرست (False) خواهد بود.
برای مثال میتوانید هنگام بررسی سن دو نفر بفهمید که آیا هر دو بالای ۲۱ سال هستند یا نه:
>>> age_0 =22>>> age_1 =18>>> age_0 >=21 and age_1 >=21False
>>> age_1 =22>>> age_0 >=21 and age_1 >=21True
در ۱ دو سن age_0 و age_1 را تعریف میکنیم. در ۲ بررسی میکنیم که آیا هر دو بالای ۲۱ اند یا نه؟ آزمون سمت چپ موفق است امّا راستی نه، پس کل عبارت نادرست (False) است. در ۳ age_1 را به ۲۲ تغییر میدهیم. اکنون مقدار age_1 بیشتر از ۲۱ است پس از دو آزمون شرطی موفّقاند، این باعث میشود که کل عبارت شرطی درست (True) تلقّی شود.
برای بهبود خوانایی میتوانید دو طرف هر آزمون شرطی پرانتز قرار دهید، هر چند این کار الزامی نیست. اگر از پرانتز استفاده کنید آزمون شما شبیه به این خواهد بود:
>>> age_0 =22>>> age_1 =18>>> age_0 >=21 or age_1 >=21True
>>> age_0 =18>>> age_0 >=21 or age_1 >=21False
از تعریف دو متغیّر مربوط به سن در ۱ شروع میکنیم. از آنجایی که آزمون مربوط به age_0 درست است، کل عبارت ۲ درست (True) ارزیابی میشود. سپس age_0 را به ۱۸ کاهش میدهیم. در آزمون ۳، هر دو آزمون رد میشوند و عبارت کلّی نادرست (False) تعبیر خواهد شد.
بررسی وجود یک مقدار در یک لیست
گاهی مهم است که پیش از انجام کاری بررسی کنیم که یک لیست مقدار خاصّی را شامل میشود یا نه. برای نمونه پیش از تکمیل ثبت نام کاربر در یک وبسایت، شاید نیاز داشته باشید که بررسی کنید آیا نام کاربریِ انتخابیِ کاربر در لیست نامهای کاربری قبلی وجود دارد یا نه.
برای دریافتن این که آیا مقدار خاصّی در لیست وجود دارد از کلیدواژهی in استفاده میکنیم. فرض کنید میخواهید برای یک پیتزافروشی برنامهای بنویسید. لیستی از مواد درخواستی مشتری برای روی پیتزا تهیّه میکنیم و سپس بررسی میکنیم که آیا آنها در لیست ما وجود دارد یا نه.
>>> requested_toppings =['mushrooms', 'onions', 'pineapple']>>> 'mushrooms' in requested_toppings
True
>>> 'pepperoni' in requested_toppings
False
در ۱ و ۲، کلیدواژهی in از پایتون میخواهد وجود ‘mushrooms’ و ‘pepperoni’ را در لیستِ requested_toppings بررسی کند. این تکنیک بسیار قدرتمند است زیرا میتوانید به کمک آن لیستی از مقادیر ضروری درست کنید و سپس به آسانی بررسی کنید که مقداری مورد نظر شما با یکی از مقادیر داخل لیست برابری میکند یا نه.
بررسی وجود نداشتن یک مقدار در یک لیست
زمانی دیگر ممکن است بخواهید وجود نداشتن مقداری را در لیستی بررسی کنید. در این شرایط میتوانید از کلیدواژهی not استفاده کنید. برای مثال لیستی از کاربران را در نظر بگیرید که از ارسال نظر در یک انجمن منع شدهاند. میخواهید پیش از ثبت یک نظر بررسی کنید که آیا کاربرِ نظردهنده در لیست کاربران ممنوع وجود دارد یا نه.
در طی آموختن برنامهنویسی جایی به عبارت بولی برخواهید خورد. عبارت بولی تنها نامی دیگر برای آزمون شرطی است. مقدار بولی همانندِ مقدارِ یک عبارتِ شرطی پس از ارزیابی، یا درست (True) است یا نادرست (False).
مقدارهای بولی غالباً برای پیگیری و دنبال کردن شرطها به کار میرود، مانند این که آیا بازی میتواند اجرا شود یا آیا کاربری میتواند مطلب خاصّی را روی وبسایت ویرایش کند یا نه:
car ='subaru'print("Is car == 'subaru'? I predict True.")
print(car =='subaru')
print("\nIs car == 'audi'? I predict False.")
print(car =='audi')
به دقّت به نتایج خود نگاه کنید و مطمئن شوید که دلیل True یا False شدن هر خط را متوجّه میشوید.
دستِ کم ده آزمون بسازید طوری که حدّاقل پنج تای آنها True و پنجتای دیگر False شود.
آزمونهای شرطی بیشتر
نباید خود را به ساختن تنها ده آزمون شرطی محدود کنید. اگر میخواهید مقایسههای بیشتری بکنید، آزمونهای بیشتری بنویسید و آنها را به conditional_tests.py بیفزایید. دست کم یک نتیجهی True و یک نتیجهی False برای هر کدام از موارد زیر داشته باشید:
آزمون برابری و نابرابری برای رشتهها
آزمونهایی با متد lower()
آزمونهای عددی شامل برابری و نابرابری، بزرگتری و کوچکتری، بزرگتری و تساوی و کوچکتری و تساوی.
آزمونهایی شامل کلیدواژههای and و or
آزمون وجود عضوی در یک لیست
آزمون وجود نداشتن عضوی در یک لیست
5.4 - گزارههای if
زمانی که آزمونهای شرطی را درک کردید میتوانید نوشتن گزارههای if را آغاز کنید. انواع مختلفی از گزارههای if وجود دارد و انتخاب آنها به تعداد شرطهایی که نیاز به آزمودن دارند بستگی دارد. در بحث مربوط به آزمونهای شرطی، مثالهای متعدّدی از عبارتهای if دیدید امّا اکنون آن را عمیقتر بررسی میکنیم.
گزارههای if ساده
سادهترین نوع گزارهی if یک آزمون و یک عملکرد دارد:
میتوانید هر نوع آزمون شرطی را در خط اوّل و هر آنچه را که میخواهید انجام دهید در بخش تورفتهی بعد از آن بنویسید. اگر آزمون شرطی True ارزیابی شود کدی که بعد از گزارهی if آمده اجرا میشود امّا اگر False ارزیابی شود پایتون آن کد را نادیده میگیرد.
اکنون متغیّری را برای نمایش سن یک نفر در نظر میگیریم و میخواهیم بدانیم آیا به سنّ قانونی برای رای دادن رسیده یا نه. کد زیر امکان رای دادن شخص را بررسی میکند:
تورفتگی در گزارههای if همان نقشی را بازی میکند که در حلقههای for ایفا میکرد. تمام خطهای تورفتهی پس از گزارهی if در صورتِ برقراریِ شرط اجرا خواهند شد و کل این خطها در صورت عدم برقراری نادیده گرفته خواهند شد.
میتوانید هر تعداد خط که خواستید به بخش بعد از گزارهی if اضافه کنید. بیایید خطّی دیگر به خروجی اضافه کنید و بپرسیم که آیا فرد برای رای دادن ثبت نام کرده است یا نه:
You are old enough to vote!
Have you registered to vote yet?
اگر مقدار age کمتر از ۱۸ باشد این برنامه هیچ خروجیای نخواهد داشت.
گزارههای if-else
اغلب میخواهید هنگام برقراری یک شرط کار خاصّی انجام دهید و در صورت عدم برقراری کار دیگری. دستور if-else پایتون چنین امکانی فراهم میآورد. ساختار if-else مشابه گزارهی if ساده است امّا گزارهی else به شما اجازه میدهد که عملی یا مجموعهای از عملها را پس از برقرار نشدن شرط اجرا کنید.
مانند مثال قبلی خطاب به کسانی که به سن رای دادن رسیدهاند پیامی چاپ میکنیم امّا این بار به کسانی که به سن قانونی نرسیدهاند هم پیامی میدهیم.
age =17if age >=18:
print("You are old enough to vote!")
print("Have you registered to vote yet?")
else:
print("Sorry, you are too young to vote.")
print("Please register to vote as soon as you turn 18!")
اگر شرط 1 برقرار شود اوّلین مجموعهدستورهای تورفتهی print() اجرا میشود. اگر آزمون False ارزیابی شود، کد else در 2 اجرا خواهد شد. چون این بار age زیر ۱۸ است، شرط برقرار نمیشود و کدی اجرا میشود که در بخش else آمده است:
Sorry, you are too young to vote.
Please register to vote as soon as you turn 18!
این کد به این دلیل کار میکند که تنها دو حالت ممکن برای ارزیابی وجود دارد: یا شخص به سن قانونی برای رای دادن رسیده یا نرسیده. در چنین شرایطی که پایتون باید یکی از دو کار ممکن را انجام دهد، ساختار if-else به خوبی کار میکند. در یک زنجیرهی if-else ساده مانند این، همیشه یکی از دو کار اجرا میشود.
5.5 - زنجیرهی if-elif-else
اغلب نیاز دارید که بیش از دو وضعیّت را بررسی کنید. برای ارزیابی چنین شرایطی میتوانید از دستور if-elif-else پایتون استفاده کنید. پایتون تنها یکی از بخشها را در زنجیرهی if-elif-else اجرا میکند. تا زمانی که یکی از آزمونها برقرار شود، پایتون آنها را به ترتیب اجرا میکند. به محض برقراری یک شرط، کد مربوط به آن اجرا شده و باقی آزمونها نادیده گرفته میشوند.
بسیاری از وضعیتها در دنیای واقعی بیش از دو شرط ممکن دارند. برای مثال یک شهربازی را در نظر بگیرید که نرخهای مختلفی برای گروههای سنّی مختلف در نظر میگیرد:
پذیرش کودکان زیر ۴ سال رایگان است.
پذیرش افراد بین ۴ تا ۱۸ سال ۲۵ دلار هزینه دارد.
پذیرش افراد ۱۸ سال و بالاتر ۴۰ دلار هزینه دارد.
چگونه میتوانیم از گزارهی if برای تعیین هزینهی پذیرش یک شخص استفاده کنیم؟ کد زیر گروه سنّی فرد را بررسی میکند و هزینهی متناسب را چاپ میکند:
age =12if age <4:
print("Your admission cost is $0.")
elif age <18:
print("Your admission cost is $25.")
else:
print("Your admission cost is $40.")
آزمون if در 1 بررسی میکند که آیا سن فرد زیر ۴ سال است یا نه. اگر این چنین باشد پیام مناسب چاپ شده و پایتون بقیهی آزمونها را نادیده میگیرد. خط elif در 2 در واقع یک if دیگر است و تنها زمانی اجرا میشود که آزمون قبلی موفّق نباشد. در این نقطه از زنجیره میدانیم که سن فرد حداقل ۴ سال است زیرا آزمون اول نتیجه نداده است. اگر سن فرد زیر ۱۸ سال باشد پیام متناسب چاپ خواهد شد و پایتون بخش else را نادیده میگیرد. اگر هر دو آزمون if و elif برقرار نشوند، کد مربوط به else را در 3 اجرا میکند.
در این مثال آزمون 1 False ارزیابی میشود پس این بخش از کد اجرا نمیشود. امّا آزمون دوم True میشود (۱۲ کمتر از ۱۸ است) پس این کد اجرا خواهد شد. خروجی یک جمله است که هزینهی پذیرش فرد را نشان میدهد:
هر سنّی بالاتر از ۱۷ موجب رد شدن هر دو آزمون اوّل میشود. در چنین شرایطی بخش else اجرا خواهد شد و هزینهی پذیرش ۴۰ دلار است.
به جای چاپ کردن هزینه در هر بخش از زنجیرهی if-elif-else، مختصرتر این است که تنها قیمت را در زنجیره مشخّص کنیم و پس از پایان ارزیابی کامل زنجیره، یک بار دستور print() ساده را فراخوانی کنیم:
age =12if age <4:
price =0elif age <18:
price =25else:
price =40print(f"Your admission cost is ${price}.")
خطها در 2،1 و 3 مقدار price را بر اساس سن فرد تعیین میکند. پس از تعیین شدن قیمت در زنجیرهی if-elif-else، فراخوانیِ جداگانه و بدونِ تورفتگی print()، این مقدار را برای گزارش هزینهی پذیرش فرد نمایش میدهد.
خروجی این کد مشابه مثال قبل است امّا مقصود زنجیرهی if-elif-else اینجا دقیقتر و مختصرتر است. به جای تعیین و چاپ کردن قیمت، تنها قیمت پذیرش را تعیین میکند. علاوه بر کارآمدی بیشتر، کد بازبینیشده نسبت به رویکرد اولیّه راحتتر قابل تغییر است. برای تغییر متن پیام خروجی، به جای تغییر سه print() مجزّا، کافی است تنها یک دستور print() را عوض کنید.
استفاده از چند elif
میتوانید هر تعداد elif که خواستید در کد خود استفاده کنید. مثلاً اگر شهربازی برای افراد مسن تخفیف قائل شده باشد، میتوانید یک آزمون شرطی دیگر به کد اضافه کنید تا تشخیص دهید فرد مستحقّ دریافت تخفیف است یا نه. فرض کنید افراد بالای ۶۵ سال باید نصف هزینهی معمول پذیرش، یعنی ۲۰ دلار، را بپردازند:
age =12if age <4:
price =0elif age <18:
price =25elif age <65:
price =40else:
price =20print(f"Your admission cost is ${price}.")
بیشترِ کد دست نخورده است. پیش از تعیین قیمت معمولی ۴۰ دلار، بخش elif دوم در 1 بررسی میکند که آیا فرد زیر ۶۵ سال است یا نه. توجّه کنید که قیمت تعیین شده در else (2) باید ۲۰ دلار باشد چون تنها افرادی که به این بخش میرسند بالای ۶۵ سال سن دارند.
حذف کردن قسمت else
پایتون الزامی به وجود یک بخش else در پایان زنجیرهی if-elif نمیبیند. گاهی وجود قسمت else سودمند است؛ گاهی به کارگیری یک گزارهی elif اضافی برای دریافتن یک شرط مشخّص مورد نظر، واضحتر است:
age =12if age <4:
price =0elif age <18:
price =25elif age <65:
price =40elif age >=65:
price =20print(f"Your admission cost is ${price}.")
قسمت elif افزوده شده در 1 برای افزاد بالاتر از ۶۵ سال قیمت ۲۰ دلار را تعیین میکند. این کار از بهکارگیری یک قسمتِ else عمومی کمی واضحتر است. با این تغییر هر قسمتِ کد با گذراندن یک آزمون شرطی مشخّص اجرا خواهد شد.
قسمت else گزارهای همهگیر است و با برقراری هر شرطی به جز آنها که در آزمونهای مشخّص if یا elif مشخّص شدهاند، اجرا میشود که میتواند گاهی شامل دادههای نامعتبر یا حتّی مخرّب باشد. اگر یک شرط پایانی مشخّص برای آزمودن دارید، حذف کردن قسمت else و اضافه کردن یک elif پایانی را در نظر داشته باشید. در نتیجه شما مطمئنتر خواهید بود که کد تنها تحت شرایط درست اجرا میشود.
5.6 - آزمودن چند شرط
زنجیرهی if-elif-else قدرتمند است امّا تنها زمانی کاربرد دارد که بخواهید فقط یک شرط برقرار باشد. به محض برقراری یکی از آزمونهای شرطی پایتون باقی آزمونها را نادیده میگیرد. این رفتار به خاطر کارآمدی سودمند است و آزمودن یک شرط مشخّص را برای شما ممکن میسازد.
با این حال گاهی مهم است که تمام شرطهای مورد نظر را بررسی کنیم. در این حالت باید از تعداد گزارهی if متوالی بدون elif یا else استفاده کنیم. این تکنیک زمانی معنا مییابد که بیش از یک شرط امکان تحقّق داشته باشند و بخواهید برای هر شرطی که True شود کاری انجام دهید.
حال مثال پیتزافروشی را دوباره بررسی میکنیم. اگر کسی دو نوع مواد مختلف برای روی پیتزای خود سفارش دهد، باید مطمئن شوید که هر دو نوع روی پیتزای او ریخته میشود:
requested_toppings = ['mushrooms', 'extra cheese']
if'mushrooms'in requested_toppings:
print("Adding mushrooms.")
if'pepperoni'in requested_toppings:
print("Adding pepperoni.")
if'extra cheese'in requested_toppings:
print("Adding extra cheese.")
print("\nFinished making your pizza!")
در 1 لیستی شامل مواد درخواستی تعریف میکنیم. در گزارهی 2 بررسی میکنیم که آیا مشتری برای روی پیتزای خود قارچ سفارش داده یا نه. اگر چنین باشد پیامی برای تایید چاپ میشود. آزمونی که در 3 برای پپرونی آورده شده یک گزارهی if ساده است، نه elif یا else، بنابراین کد مربوط به آن، صرف از نظر از برقراریِ شرطِ قبلی، اجرا میشود. کد خط 4 بررسی میکند که بدون توجّه به آزمون قبلی، آیا پنیر اضافی درخواست شده یا نه. این سه آزمون مستقل با هر بار اجرای برنامه اجرا خواهند شد.
از آنجایی که تمام شرطها در این مثال ارزیابی میشوند، قارچ و پنیر اضافی هر دو به پیتزا اضافه میشوند:
Adding mushrooms.
Adding extra cheese.
Finished making your pizza!
اگر از ساختار if-elif-else استفاده میکردیم این کد به درستی کار نمیکرد، زیرا پس از برقراری یکی از شرطها اجرای آن متوقّف میشد. اینجا میبینیم که خروجی به چه شکل خواهد بود:
requested_toppings = ['mushrooms', 'extra cheese']
if'mushrooms'in requested_toppings:
print("Adding mushrooms.")
elif'pepperoni'in requested_toppings:
print("Adding pepperoni.")
elif'extra cheese'in requested_toppings:
print("Adding extra cheese.")
print("\nFinished making your pizza!")
آزمون مربوط به ‘mushrooms’ اولین موردی است که تحقّق مییابد پس قارچ به پیتزا اضافه میشود. با این حال مقادیر ’extra cheese’ و ‘pepperoni’ هرگز بررسی نمیشوند زیرا پس از موفقّیتآمیز بودن آزمون اوّل، پایتون کدهای دیگر در زنجیرهی if-elif-else را اجرا نمیکند. اوّلین مورد درخواستی مشتری اضافه میشود امّا دوتای دیگر از قلم خواهند افتاد:
به طور مختصر، اگر بخواهید تنها یک قسمت از کد اجرا شود از یک زنجیرهی if-elif-else استفاده کنید. اگر قسمتهای مختلف کد باید اجرا شوند از تعدادی گزارهی if مستقل بهره بگیرید.
خودآزمایی
رنگهای موجودات بیگانه ۱
فرض کنید که در یک بازی به یک موجود بیگانه شلیک کردهایم. متغیّری به نام alien_color تعریف کنید و به آن مقدار ‘green’، ‘yellow’ یا ‘red’ را نسبت دهید.
یک گزارهی if بنویسید تا سبز بودن رنگ موجود بیگانه را بررسی کند. اگر چنین بود پیامی چاپ کند که بازیکن ۵ امتیاز گرفته است.
یک نسخه از این برنامه را به نحوی بنویسید که در آن شرط برقرار میشود. نسخهی دیگر را طوری بنویسید که در آن شرط تحقّق نیابد. (در این نسخه برنامه هیچ خروجیای ندارد.)
رنگهای موجودات بیگانه ۲
همانند تمرین ۵-۳ رنگی برای موجودات بیگانه انتخاب کنید و یک زنجیرهی if-else بنویسید.
اگر رنگ موجود بیگانه سبز باشد، پیامی چاپ کند مبنی بر این که بازیکن ۵ امتیاز کسب کرده است.
اگر رنگ سبز نبود اعلام کند که بازیکن ۱۰ امتیاز گرفته است.
برنامه را یک بار طوری بنویسید که شرط if برقرار شود و بار دیگر شرط else.
رنگهای موجودات بیگانه ۳
زنجیرهی if-else مثال ۵-۴ را به if-elif-else تبدیل کنید.
اگر رنگ موجود بیگانه سبز بود، پیامی چاپ کند که بازیکن ۵ امتیاز گرفته است.
اگر زرد بود، پیامی چاپ کند که بازیکن ۱۰ امتیاز گرفته است.
اگر قرمز بود، پیامی چاپ کند که بازیکن ۱۵ امتیاز گرفته است.
سه نسخه از این برنامه را به نحوی بنویسید که هر بار یکی از شرطها برقرار شود.
مراحل زندگی
یک زنجیرهی if-elif-else بنویسید که مشخّص کند فرد در کدام مرحلهی زندگی است. مقداری برای متغیّر age تعیین کنید، سپس:
اگر فرد زیر ۲ سال سن داشت، پیامی چاپ کند که فرد نوزاد است.
اگر سن بین ۲ تا ۴ سال بود پیامی چاپ کند که فرد نوپاست.
اگر فرد بین ۴ و ۱۳ سال سن دارد، پیامی چاپ کند که فرد کودک است.
اگر سن بین ۱۳ و ۲۰ سال است پیامی چاپ کند که فرد نوجوان است.
اگر سن بین ۲۰ و ۶۵ باشد پیامی چاپ کند که فرد بالغ است.
اگر سن ۶۵ یا بیشتر بود پیامی چاپ کند که فرد کهنسال است.
میوهی مورد علاقه
لیستی از میوههای مورد علاقهی خود درست کنید و تعدادی گزارهی if مستقل بنویسید که وجود میوهی خاصّی را در لیست بررسی کند.
لیستی از سه میوهی مورد علاقهی خود بسازید و آن را favorite_fruits بنامید.
پنج گزارهی if بنویسید. هر کدام از آنها بررسی میکنند که آیا میوهی خاصّی در لیست وجود دارد یا نه. اگر میوه در لیست بود کد داخل گزارهی if باید جملهای مانند You really like bananas چاپ کند.
5.7 - بهکارگیری گزارههای if در لیستها
با ترکیب کردن لیستها و گزارههای if میتوانید کارهای جالبی بکنید. میتوان مقادیر ویژهای را تحت نظر گرفت که نسبت به دیگر مقادیر موجود در لیست نیاز به عمل متفاوتی دارند، میتوانید شرایطی را که در حال تغییرند به طور موثری مدیریّت کنید. به تدریج ثابت میکنید که کد شما همانطور که انتظار دارید تحت هر شرایطی کار میکند.
بررسی موارد خاص
این فصل با مثالی ساده آغاز شد که در آن نشان دادیم چگونه مقداری ویژهای مانند ‘bmw’ را مدیریت کنیم به نحوی که در مقایسه با دیگر اعضای لیست به شکل دیگری چاپ شود. اکنون که درکی پایهای از آزمونهای شرطی و گزارههای if کسب کردهاید، نگاهی دقیقتر به نحوهی تحت نظر گرفتن مقدارهای خاصی در یک لیست میاندازیم و این که چگونه آنها را به درستی مدیریت کنیم.
اجازه دهید با مثال پیتزافروشی ادامه بدهیم. این پیتزافروشی همزمان با آماده شدن پیتزا، پیامی نشان میدهد که مواد درخواستی به پیتزا اضافه شده است. با ساختن لیستی از مواد روی پیتزا که مشتری درخواست کرده، کد این برنامه بسیار بهینهتر نوشته میشود. سپس به کمک یک حلقه اعلام میکنیم که هر مورد درخواستی به پیتزا اضافه شده است:
requested_toppings = ['mushrooms', 'green peppers', 'extra cheese']
for requested_topping in requested_toppings:
print(f"Adding {requested_topping}.")
print("\nFinished making your pizza!")
خروجی سرراست است زیرا کد تنها یک حلقهی سادهی for است:
requested_toppings = ['mushrooms', 'green peppers', 'extra cheese']
for requested_topping in requested_toppings:
if requested_topping =='green peppers':
print("Sorry, we are out of green peppers right now.")
else:
print(f"Adding {requested_topping}.")
print("\nFinished making your pizza!")
این بار هر مورد درخواستی را پیش از افزودن به پیتزا بررسی میکنیم. کد در 1 بررسی میکند که آیا مشتری فلفل سبز درخواست کرده یا نه. اگر چنین باشد پیامی چاپ میکند و به او اعلام میکند که چرا نمیتوانند چنین موردی را به پیتزای خود اضافه کنند. قسمت else در 2 اطمینان حاصل میکند که تمام مواد دیگر به پیتزا افزوده شوند.
خروجی نشان میدهد که هر کدام از موارد درخواستی به درستی مدیریّت شدهاند:
Adding mushrooms.
Sorry, we are out of green peppers right now.
Adding extra cheese.
Finished making your pizza!
بررسی تهی نبودن یک لیست
تا اینجا در مورد لیستهایی که با آنها کار کردهایم یک فرض ساده داشتهایم؛ فرض کردهایم که هر لیست حداقل یک عضو دارد. به زودی به کاربران امکان وارد کردن اطّلاعاتی را خواهیم داد که باید در لیستها ذخیره شود، بنابراین نمیتوانیم فرض کنیم که در هر بار اجرای حلقه لیست عضوی در خود دارد. در چنین شرایط بررسی تهی بودنِ یک لیست پیش از اجرای حلقهی for کاری سودمند است.
به عنوان یک مثال پیش از پختن پیتزا بررسی میکنیم که آیا لیست مواد درخواستی خالی است یا نه؟ اگر لیست خالی بود به کاربر اعلام میکنیم که آیا مطمئن است پیتزایی ساده میخواهد؟ اگر لیست خالی نبود پیتزا را همانند مثالهای قبلی درست میکنیم:
requested_toppings = []
if requested_toppings:
for requested_topping in requested_toppings:
print(f"Adding {requested_topping}.")
print("\nFinished making your pizza!")
else:
print("Are you sure you want a plain pizza?")
این بار از یک لیست خالی برای مواد درخواستی در 1 شروع کردیم. به جای پریدن داخل حلقهی for، یک آزمون سریع در 2 انجام دادیم. اگر در گزارهی if نام لیست را بیاوریم، در صورتی که لیست دستِ کم یک عضو داشته باشد، پایتون این گزاره را True ارزیابی میکند و اگر لیست خالی باشد آن را False در نظر میگیرد. اگر requested_toppings در آزمون شرطی قبول شود، حلقهی for مانند مثال قبل اجرا خواهد شد. در غیر این صورت در 3 پیامی چاپ میکنیم و از مشتری میپرسیم که آیا واقعاً یک پیتزای ساده میخواهد.
در این مورد لیست خالی است پس خروجی از کاربر میپرسد آیا واقعاً پیتزای ساده میخواهد؟
اگر لیست خالی نباشد خروجی همهی چیزهایی را که به پیتزا اضافه میشود نشان میدهد.
به کارگیری چند لیست
مشتریان ممکن است هر چیزی درخواست کنند به ویژه هنگامی که بحث پیتزا پیش میآید. اگر یک مشتری بخواهد روی پیتزای او سیبزمینی سرخکرده ریخته شود چه؟ میتوانید از لیستها و گزارههای if استفاده کنید تا پیش از انجام هر عملی روی ورودی کاربر، مطمئن شوید که درخواست او معنادار است یا نه.
حال پیش از پختن پیتزا درخواست ریختن مواد نامعمول روی آن را زیر نظر میگیریم. مثال زیر دو لیست را تعریف میکند. اولی لیست مواد موجود در پیتزافروشی و دیگری لیست موادی است که مشتری درخواست کرده است. این بار هر مورد requested_toppings پیش از اضافه شدن به پیتزا با موارد موجود مقایسه میشود:
available_toppings = ['mushrooms', 'olives', 'green peppers',
'pepperoni', 'pineapple', 'extra cheese']
requested_toppings = ['mushrooms', 'french fries', 'extra cheese']
for requested_topping in requested_toppings:
if requested_topping in available_toppings:
print(f"Adding {requested_topping}.")
else:
print(f"Sorry, we don't have {requested_topping}.")
print("\nFinished making your pizza!")
در 1 لیستی از مواد موجود در پیتزافروشی تعریف کردیم. توجّه داشته باشید که اگر انتخابهای موجود برای مواد روی پیتزا ثابت باشد میتوان به جای لیست از چندتایی استفاده کرد. در 2 لیستی از موادِ موردِ درخواستِ مشتری درست کردیم. به درخواست نامعمول ‘french fries’ توجّه کنید. در خط 3 داخل لیست مواد درخواستی حلقه میزنیم. درون حلقه (4) ابتدا بررسی میکنیم که آیا مادهی درخواستی در لیست مواد موجود وجود دارد یا نه. اگر چنین است مواد را به پیتزا اضافه میکنیم. امّا اگر مادهی درخواستی در لیست موجود نبود، بخش else خط 5 را اجرا خواهد کرد. این بخش پیامی چاپ میکند و به مشتری میگوید که مواد مورد نظر او موجود نیست.
Adding mushrooms.
Sorry, we don't have french fries.
Adding extra cheese.
Finished making your pizza!
در تنها چند خط کد یک مسالهی دنیای واقعی را به روشی بسیار موثّر مدیریت کردیم!
خودآزمایی
سلامْ مدیر
لیستی از پنجتا یا بیشتر نام کاربری درست کنید که شامل ‘admin’ باشد. فرض کنید کدی مینویسید که پس از ورود هر کاربر به سایت پیام تبریکی خطاب به او چاپ کند. در لیست حلقه بزنید و پیام تبریکی برای هر کاربر چاپ کنید:
اگر نام کاربری ‘admin’ است تبریکی خاص چاپ کنید مانند Hello admin, would you like to see a status report?.
در غیر این صورت یک پیام تبریک عمومی چاپ کنید. مانند Hello Jaden, thank you for logging in again.
فقدان کاربران
یک آزمون if به hello_admin.py اضافه کنید تا مطمئن شوید که لیست کاربران خالی نیست.
اگر لیست خالی است پیام We need to find some users را چاپ کند.
تمام نامهای کاربری را از لیست خود حذف کنید و مطمئن شوید که پیام درست چاپ میشود.
بررسی نامهای کاربری
کارهای زیر را برای ساختن برنامهای انجام دهید که چگونگیِ عملکردِ وبسایتها را هنگامِ بررسیِ یکتا بودنِ نامِ کاربریِ هر کس شبیهسازی کند.
لیستی به نام current_users شامل پنجتا یا بیشتر نام کاربری بسازید.
لیست دیگری به نام new_users شامل پنج نام کاربری دیگر درست کنید. مطمئن شوید یک یا دو نام این لیست با لیست current_users مشترک است.
در لیست new_users حلقه بزنید تا ببینید که نام کاربری جدید قبلاً استفاده شده یا نه. اگر چنین بود پیامی چاپ کنید که به کاربر بگوید باید نام کاربر جدیدی انتخاب کند. اگر نام کاربری قبلاً استفاده نشده بود پیامی چاپ کند که اعلام کند نام کاربری موجود است.
مطمئن شوید که مقایسهی شما به بزرگ و کوچکی حروف حساس نیست. اگر ‘John’ قبلاً انتخاب شده باشد، ‘JOHN’ نباید پذیرفته شود. (برای این کار باید یک کپی از current_users شامل نام کاربران با حروف کوچک درست کنید.)
اعداد ترتیبی
اعداد ترتیبی جایگاه خود را در یک لیست نمایش میدهند مثل یکم، دوم و … . در زبان انگلیسی به جز ۱، ۲ و ۳، باقی اعداد ترتیبی به th ختم میشوند.
اعداد ۱ تا ۹ را در لیستی ذخیره کنید.
در لیست حلقه بزنید.
از یک زنجیرهی if-elif-else در داخل حلقه استفاده کنید تا پسوند مناسب ترتیبی را برای هر عدد چاپ کند. خروجی شما باید به صورت “1st 2nd 3rd 4th 5th 6th 7th 8th 9th” خوانده شود و هر نتیجه باید در یک خط جدا چاپ شود.
5.8 - شکلدهی به عبارتهای if
در هر کدام از مثالهای این فصل نمونههایی از عادتهای خوب شکلدهی را دیدهاید. تنها توصیهای که PEP 8 برای شکلدهی آزمونهای شرطی ارائه کرده این است که در دو طرف عملگرهای مقایسهای مانند ==، >= و <= یک فاصلهی خالی قرار دهید.
این فاصلهگذاری بر روشی که پایتون کد شما را تفسیر میکند، تاثیری نمیگذارد؛ تنها خواندن کد شما را برای خودتان و دیگران سادهتر میکند.
خودآزمایی
شکلدهی به گزارههای if
برنامههایی که در این فصل نوشتهاید مرور کنید و مطمئن شوید که آزمونهای شرطی خود را به خوبی شکل دادهاید.
ایدههای شما
هماکنون شما نسبت به زمانی که این کتاب را شروع کردید برنامهنویس تواناتری هستید. اکنون که از روش مدل شدن وضعیتهای موجود در دنیای واقعی در برنامهها درک بهتری پیدا کردهاید، میتوانید به برخی مشکلات فکر کنید که میتوانید با برنامههای خود آن را برطرف کنید. همزمان با توسعهی مهارتهای برنامهنویسی خود، اگر دربارهی مسائلی که میتوانید حل کنید ایدهای دارید، آن را ثبت کنید. بازیهایی را در نظر بگیرید که دوست دارید بسازید، یا مجموعهدادههایی که میخواهید بکاوید یا برنامههای تحت وبی که میخواهید ایجاد کنید.
5.9 - خلاصه
در این فصل نوشتن آزمونهای شرطی را آموختید که همیشه True یا False ارزیابی میشوند. یاد گرفتید که چگونه عبارتهای سادهی if، زنجیرههای if-else و if-elif-else بنویسید. برای تشخیص شرطهای خاصی که نیاز به بررسی دارند، بهکارگیری این ساختارها را شروع کردید. همچنین فهمیدید که آن شرطها چه زمانی در برنامههای شما برآورده میشوند. برخورد متفاوت با برخی اعضای خاص یک لیست را همزمان با بهکارگیری کارآمدِ یک حلقهی for آموختید. دوباره به توصیهها و شیوهنامههای پایتون برخوردید تا مطمئن شوید با پیچیدهتر شدن برنامهها، خوانایی و درک آنها نسبتاً ساده باقی میماند.
در فصل ۶ در مورد دیکشنریهای پایتون میآموزید. دیکشنری شبیه لیست است امّا امکان به هم پیوستن اطلاعات مختلف را فراهم میآورد. ساختن دیکشنریها، حلقه زدن در آنها و استفاده کردن از آنها را در کنار لیستها و گزارههای if فرامیگیرید. آموختن در مورد دیکشنریها به شما امکان مدل کردن گسترهی بزرگتری از وضعیتهای دنیای واقعی را میدهد.
6 - دیکشنریها
6.1 - یک دیکشنری ساده
در این فصل نحوهی بهکارگیری دیکشنریها را در پایتون میآموزید که به شما امکان متّصل کردن تکههایی از اطّلاعاتِ مرتبط را میدهند. یاد میگیرید که چگونه پس از وارد کردن اطّلاعات به دیکشنری، به آن دسترسی پیدا کنید و چگونه آنها را تغییر دهید. ازآنجاییکه میتوان حجم نامحدودی از اطّلاعات را در یک دیکشنری ذخیره کرد، طریقهی حلقه زدن در دادههای یک دیکشنری را خواهید آموخت. علاوه بر این قرار دادن دیکشنریها در لیستها، لیستها داخل دیکشنریها و حتّی دیکشنریها را در دیکشنریهای دیگر میآموزید.
درک کردن دیکشنریها به شما امکان میدهد که گسترهای از مسائل دنیای واقعی را به طور دقیقتر مدل کنید.
یک دیکشنری ساده
بازیای را در نظر بگیرید که در آن تعداد موجود بیگانه با رنگها و ارزش امتیازی مختلف وجود دارد. این دیکشنری ساده اطّلاعات مربوط به یک موجود خاص را در بر میگیرد:
همانندِ بیشترِ مفاهیمِ جدیدِ برنامهنویسی، به کار گرفتن دیکشنریها نیازمند تمرین است. زمانی که کمی با دیکشنریها کار کردید درخواهید یافت که آنها چگونه با کارآمدی وضعیتهای دنیای واقعی را مدل میکنند.
6.2 - کار کردن با دیکشنری
دیکشنری در پایتون مجموعهای از جفتهای کلید-مقدار است. هر کلید با یک مقدار مرتبط است و به کمک آن میتوانید به این مقدارِ مرتبط دسترسی پیدا کنید. مقدار یک کلید میتواند عدد، رشته، لیست یا حتّی یک دیکشنری دیگر باشد.
در پایتون یک دیکشنری با دنبالهای از کلید-مقدارها، داخل آکُلاد {} قرار میگیرد:
یک جفت کلید-مقدار مجموعهای از مقدارهای به هم مرتبط است. پایتون با ارائهی هر کلید، مقداری را که با آن در ارتباط است برمیگرداند. هر کلید با یک دونقطه به مقدار خود وصل میشود و جفتهای کلید-مقدارِ مجزّا با کاما از یکدیگر جدا میشوند.
دسترسی به مقادیر یک دیکشنری
برای گرفتن مقدار مرتبط با یک کلید، نام دیکشنری را بنویسید و سپس کلید را داخل یک جفت کروشه جلوی آن قرار دهید:
شما میتوانید تعداد نامحدودی جفتِ کلید-مقدار در یک دیکشنری بگنجانید. اگر بازیکنی این موجود بیگانه را بکشد، امتیازِ کسبشدهی او با کدی به صورت زیر به دست میآید:
کد 1 میتواند بلافاصله بعد از تعریفِ دیکشنری، مقدار مرتبط با کلید ‘points’ را از آن بیرون بکشد. این مقدار در ادامه به متغیّر new_points منسوب میشود. خط 2 دربارهی تعداد امتیازی که بازیکن دریافت کرده عبارتی چاپ میکند:
دیکشنریها ساختارهایی پویا هستند و همیشه میتوان جفتهای کلید-مقدارِ جدید را به آنها افزود. برای افزودن یک جفت کلید-مقدار، نام دیکشنری را همراه با کلید جدید، که در کروشه قرار گرفته، در کنارِ مقدار جدید مینویسید.
بیایید دو دانستهی جدید به دیکشنری alien_0 بیفزاییم: مختصّات x و y آن:
مطابق قبل از تعریف یک دیکشنری شروع میکنیم. سپس آن را چاپ میکنیم. در 1 یک جفت کلید-مقدار جدید به آن اضافه میکنیم: کلید ‘x_position’ و مقدار 0. در 2 همین کار را برای کلید ‘y_position’ تکرار میکنیم:
از پایتون ۳.۷ به بعد، دیکشنریها ترتیبی را که مطابق آن تعریف شدهاند، حفظ میکنند. زمانی که یک دیکشنری را چاپ میکنید یا در آن حلقه میزنید، اعضای آن را به همان ترتیبِ افزوده شدن خواهید دید.
شروع کردن از یک دیکشنری خالی
گاهی سادهتر یا حتّی الزامی است که کار را از یک دیکشنری خالی شروع کرده و سپس موارد جدید را به آن اضافه کنیم:
معمولاً هنگام ذخیرهسازی دادههایی که کاربران ارائه میکنند یا نوشتن کدی که تعداد زیادی جفت کلید-مقدار را به طور خودکار تولید میکند، از دیکشنریهای خالی استفاده میکنیم.
تغییر دادن مقدارها در یک دیکشنری
برای تغییر دادن مقداری در یک دیکشنری، نام آن را به همراه کلید در داخل کروشه بنویسید و سپس مقدار جدیدی را بیاورید:
alien_0 = {'x_position': 0, 'y_position': 25, 'speed': 'medium'}
print(f"Original position: {alien_0['x_position']}")
# Move the alien to the right.# Determine how far to move the alien based on its current speed.if alien_0['speed'] =='slow':
x_increment =1elif alien_0['speed'] =='medium':
x_increment =2else:
# This must be a fast alien. x_increment =3# The new position is the old position plus the increment.alien_0['x_position'] = alien_0['x_position'] + x_increment
print(f"New position: {alien_0['x_position']}")
در 1 یک زنجیرهی if-elif-else مشخّص میکند که موجود بیگانه چهقدر باید به سمت راست حرکت کند. پس از محاسبه شدن مقدارِ افزایشِ جابهجایی، آن را در 2 به مقدار x_position اضافه میکنیم:
توجّه داشته باشید که جفت کلید-مقدار به صورت همیشگی حذف خواهد شد.
6.3 - یک دیکشنری شامل اشیای مشابه
مثال قبلی به ذخیرهسازی اطّلاعاتِ مختلفِ یک شیء میپرداخت که موجودی بیگانهای در یک بازی بود. علاوه بر این حالت شما میتوانید از دیکشنری برای ذخیره کردن یک نوع اطّلاعات در مورد اشیاء مختلف استفاده کنید. مثلاً فرض کنید میخواهید از افراد مختلف در مورد زبان برنامهنویسی موردعلاقهشان نظرسنجی کنید:
اگر نشانوند دوم را هنگام فراخوانی get() تعیین نکنید و کلید مورد نظر وجود نداشته باشد، پایتون مقدار None را برمیگرداند. مقدار ویژهی None به معنای «وجود نداشتنِ هیچ مقداری» است. این خطا نیست بلکه مقداری ویژه است که فقدان یک مقدار را مشخّص میکند.
خودآزمایی
فرد
از یک دیکشنری برای ذخیرهسازی اطّلاعات مربوط به فردی استفاده کنید که او را میشناسید. نام و نام خانوادگی، سن و شهرِ محل زندگی او را ضبط کنید. باید کلیدهایی مانند first_name، last_name، age و city داشته باشید. هر بخشِ اطّلاعاتی را که ذخیره کردهاید چاپ کنید.
اعداد محبوب
از یک دیکشنری برای ذخیره کردن اعداد محبوبِ افراد استفاده کنید. پنج نام در نظر بگیرید و آنها را به عنوان کلیدهای یک دیکشنری به کار بگیرید. اعدادی را به عنوان عدد محبوب هر فرد به جای مقدارهای هر کلید در دیکشنری ذخیره کنید. نام هر کس و عدد مورد علاقهی او را چاپ کنید.
واژهنامه
دیکشنری پایتون میتواند به عنوان دیکشنری (واژهنامهی) واقعی به کار رود.
پنج واژهی مربوط به برنامهنویسی را در نظر بگیرید که در فصلهای گذشته معنی آنها را آموختهاید. از واژهها به عنوان کلید و از معنی آنها به جای مقدار استفاده کرده و آنها را در دیکشنری ذخیره کنید.
هر واژه و معنای آن را به شکلی منظّم در خروجی چاپ کنید.
6.4 - حلقه زدن در یک دیکشنری
یک دیکشنری پایتون میتواند شامل تنها چند جفت کلید-مقدار یا میلیونها عدد از آنها باشد. چون دیکشنری قابلیّت ذخیرهسازی انبوهی از اطّلاعات را دارد، پایتون این امکان را برای شما فراهم میآورد تا در یک دیکشنری حلقه بزنید. میتوانید در تمام جفتهای کلید-مقدار یک دیکشنری، در کلیدها و یا در مقدارهای آن حلقه بزنید.
حلقه زدن در تمام جفتهای کلید-مقدار
دیکشنری زیر نام کاربری، نام و نام خانوادگی یک فرد را ذخیره میکند:
user_0 = {
'username': 'efermi',
'first': 'enrico',
'last': 'fermi',
}
for key, value in user_0.items():
print(f"\nKey: {key}")
print(f"Value: {value}")
برای نوشتن یک حلقهی for برای دیکشنری، دو متغیّر را نامگذاری میکنیم که کلید و مقدار یک جفت کلید-مقدار را نگهداری میکند. بخش دوم عبارت for در 1، نام دیکشنری را در بر میگیرد که متد items() پس از آن آمده است. این متد لیستی از جفتهای کلید-مقدار برمیگرداند:
Key: last
Value: Fermi
Key: first
Value: enrico
Key: username
Value: efermi
برای مثال favorite_languages.py که در صفحهی قبل دیدیم، اگر داخل دیکشنری favorite_languages حلقه بزنید نام هر فرد و زبان برنامهنویسی مورد علاقهی او را به دست میآورید:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
for name, language in favorite_languages.items():
print(f"{name.title()}'s favorite language is {language.title()}.")
در واقع هنگام حلقه زدن در یک دیکشنری، رفتار پیشفرض همان حلقه زدن در کلیدها است، پس اگر به جای for name in favorite_languages.keys(): این کد را مینوشتید: for name in favorite_languages: نتیجه یکسان میشد.
در داخل یک حلقه و به کمک کلید فعلی، امکان دسترسی به مقدار آن کلید وجود دارد. اکنون خطاب به دوستان خود پیامی حاوی زبان برنامهنویسی مورد علاقهی آنها چاپ میکنیم:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
friends = ['phil', 'sarah']
for name in favorite_languages.keys():
print(name.title())
if name in friends:
language = favorite_languages[name].title()
print(f"\t{name.title()}, I see you love {language}!")
از پایتون ۳.۷ به بعد حلقه زدن در دیکشنری اعضای آن را به همان ترتیبِ ذخیره شدن باز میگرداند. با این حال گاهی به حلقه زدن در دیکشنری با ترتیبی متفاوت نیاز دارید:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
for name in sorted(favorite_languages.keys()):
print(f"{name.title()}, thank you for taking the poll.")
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
print("The following languages have been mentioned:")
for language in favorite_languages.values():
print(language.title())
The following languages have been mentioned:
Python
C
Python
Ruby
این رویکرد تمام مقدارهای دیکشنری را بیرون میکشد بدون این که به تکراری بودن یا نبودن آنها اهمّیّتی دهد. برای این که زبانهای برنامهنویسی انتخابی را بدون تکرار ببینیم، از مجموعه استفاده میکنیم:
favorite_languages = {
'jen': 'python',
'sarah': 'c',
'edward': 'ruby',
'phil': 'python',
}
print("The following languages have been mentioned:")
for language in set(favorite_languages.values()):
print(language.title())
>>> languages = {'python', 'ruby', 'python', 'c'}
>>> languages
{'ruby', 'python', 'c'}
اشتباه گرفتن مجموعه و دیکشنری به سادگی امکانپذیر است زیرا هر دو در آکُلاد قرار میگیرند. اگر به آکُلادهایی برخوردید که درون آن جفتهای کلید-مقدار نبود، احتمالاً با یک مجموعه سر و کار دارید.
خودآزمایی
واژهنامه ۲
حال که حلقه زدن در دیکشنری را آموختهاید، کد تمرین ۶-۳ را تمیز کنید به طوری که به جای تعدادی print() از یک حلقه برای نمایش کلیدها و مقدارهای دیکشنری استفاده شود. پنج عبارت پایتونی دیگر به واژهنامهی خود بیفزایید.
رودخانهها
یک دیکشنری بسازید که شامل سه رودخانهی اصلی و کشوری که آن رودخانهها در آن جریان دارند، باشد.
از یک حلقه برای چاپ کردن جملهای در مورد رودخانه استفاده کنید، مثلاً: The Nile runs through Egypt.
از حلقهای برای چاپ نام تکتکِ رودخانههای داخل دیکشنری استفاده کنید.
از حلقهای برای چاپ نام تکتکِ کشورهای داخل دیکشنری استفاده کنید.
نظرسنجی
از کد favorite_languages.py استفاده کنید.
لیستی از افرادی که باید در نظرسنجی شرکت کنند، تهیه کنید که شامل چند نام موجود در دیکشنری و چند نام جدید باشد.
در لیست افرادی که باید شرکت کنند حلقه بزنید. اگر پیش از این در نظرسنجی شرکت کرده بودند، پیام تشکری خطاب به آنها چاپ کنید. اگر نه پیامی چاپ کنید و از آنها برای شرکت در نظرسنجی دعوت به عمل آورید.
6.5 - تودرتوکردن
گاهی میخواهید چند دیکشنری را درون یک لیست یا لیستی از موارد را به عنوان مقدار در یک دیکشنری قرار دهیم. به این کار تودرتو کردن میگویند. میتوانید دیکشنریها را داخل یک لیست، لیستی را داخل یک دیکشنری و حتّی یک دیکشنری را داخل یک دیکشنری دیگر قرار دهید.
لیستی از دیکشنریها
دیکشنری alien_0 شامل اطّلاعات مختلفی از یک موجود بیگانه است امّا دیگر برای ذخیره کردنِ اطّلاعاتِ موجودِ بیگانهی دوم جا ندارد. یک راه ساختن لیستی از دیکشنریهاست:
یک مثال واقعیتر ساختن بیشتر از سه موجود به کمک کدی است که تولید موجودات بیگانه را به طور خودکار انجام میدهد. در مثال زیر از range() برای ساختن ناوگانی از ۳۰ موجود بیگانه استفاده میکنیم:
# Make an empty list for storing aliens.aliens = []
# Make 30 green aliens.for alien_number in range(30):
new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
aliens.append(new_alien)
# Show the first 5 aliens.for alien in aliens[:5]:
print(alien)
print("...")
# Show how many aliens have been created.print(f"Total number of aliens: {len(aliens)}")
این موجودهای بیگانه همگی خصوصیّاتی مشترک دارند ولی پایتون هر کدام از آنها را یک شیء جدا در نظر میگیرد که امکان تغییر دادن جداگانهی هر کدام از آنها در آینده فراهم است.
چگونه میتوانید با چنین گروهی از موجودات بیگانه کار کنید؟ تصوّر کنید که بخشی از بازی موجوداتی دارد که رنگ آنها عوض میشود:
aliens = []
for alien_number in range(30):
new_alien = {'color': 'green', 'points': 5, 'speed': 'slow'}
aliens.append(new_alien)
for alien in aliens[:3]:
if alien['color'] =='green':
alien['color'] ='yellow' alien['speed'] ='medium' alien['points'] =10# Show the first 5 aliens.for alien in aliens[:5]:
print(alien)
print("...")
for alien in aliens[0:3]:
if alien['color'] =='green':
alien['color'] ='yellow' alien['speed'] ='medium' alien['points'] =10elif alien['color'] =='yellow':
alien['color'] ='red' alien['speed'] ='fast' alien['points'] =15
لیستی در یک دیکشنری
گاهی سودمند است که به جای قرار دادن یک دیکشنری درونِ یک لیست، لیستی را درون یک دیکشنری بگذاریم. برای نمونه به این فکر کنید که چگونه میتوان پیتزایی را توصیف کرد که یک نفر سفارش میدهد:
# Store information about a pizza being ordered.pizza = {
'crust': 'thick',
'toppings': ['mushrooms', 'extra cheese'],
}
# Summarize the order.print(f"You ordered a {pizza['crust']}-crust pizza ""with the following toppings:")
for topping in pizza['toppings']:
print("\t"+ topping)
favorite_languages = {
'jen': ['python', 'ruby'],
'sarah': ['c'],
'edward': ['ruby', 'go'],
'phil': ['python', 'haskell'],
}
for name, languages in favorite_languages.items():
print(f"\n{name.title()}'s favorite languages are:")
for language in languages:
print(f"\t{language.title()}")
Jen's favorite languages are:
Python
Ruby
Sarah's favorite languages are:
C
Phil's favorite languages are:
Python
Haskell
Edward's favorite languages are:
Ruby
Go
نکته
نباید بیش از اندازه لیستها و دیکشنریها را در هم به کار ببرید. اگر بیش از آنکه در مثالهای این فصل دیدید، نیاز به داخل کردن این موارد در یکدیگر پیدا کردید، احتمالاً راه حل سادهتری برای حل آن مساله وجود دارد.
یک دیکشنری در یک دیکشنری دیگر
امکان قرار دادن یک دیکشنری درون یک دیکشنری دیگر وجود دارد:
Username: aeinstein
Full name: Albert Einstein
Location: Princeton
Username: mcurie
Full name: Marie Curie
Location: Paris
نکته
توجّه کنید که ساختار دیکشنری هر کاربر یکسان است. هر چند چنین چیزی در پایتون اجباری نیست، امّا این ساختار یکسان تودرتو کردن دیکشنریها را سادهتر میکند.
خودآزمایی
مردم
از برنامهای شروع کنید که در تمرین ۶-۱ نوشتید. دو دیکشنری جدید در مورد افراد مختلف بسازید و هر سه دیکشنری را در لیستی به نام people ذخیره کنید. در لیست حلقه بزنید و حین این کار، هر چه در مورد هر فرد میدانید چاپ کنید.
حیوانات خانگی
چند دیکشنری بسازید که هر کدام بیانگر یک حیوان خانگی باشد. در هر دیکشنری نوع حیوان و نام صاحب آن را بیاورید. این دیکشنریها را در لیستی به نام pets ذخیره کنید. سپس در لیست حلقه بزنید و همزمان هر چه در مورد هر کدام از حیوانها میدانید چاپ کنید.
مکانهای مورد علاقه
یک دیکشنری به نام favorite_places بسازید. نام سه نفر را به عنوان کلیدهای این دیکشنری و نام سه مکان دلخواه را برای هر کس به عنوان مقدار این کلیدها ذخیره کنید. در دیکشنری حلقه بزنید و نام و مکان مورد علاقهی هر کس را چاپ کنید.
نامهای مورد علاقه
برنامهی تمرین ۶-۲ را تغییر دهید به طوریکه هر فرد بتواند بیش از یک عدد دلخواه داشته باشد. سپس نام هر فرد و اعداد مورد علاقهی او را چاپ کنید.
شهرها
یک دیکشنری به نام cities بسازید. نام سه شهر را به عنوان کلیدهای آن در نظر بگیرید. برای اطّلاعات هر شهر یک دیکشنری بسازید که شامل این موارد باشد: کشوری که آن شهر در آن است، جمعیّت تقریبی و یک دانستنی در مورد آن.
توسعهها
از یکی از برنامههای نمونهی این فصل استفاده کنید و با افزودن کلیدها و مقدارها، تغییر بستر و مفهوم برنامه و بهبود شکل خروجی، آن را توسعه دهید.
6.6 - خلاصه
در این فصل روش تعریف کردن یک دیکشنری و نحوهی کار کردن با اطّلاعات ذخیره شده در آن را آموختید. یاد گرفتید که چگونه به اعضای دیکشنری به طور جداگانه دسترسی پیدا کنید، آنها را تغییر دهید و این که چگونه در تمام اطّلاعات یک دیکشنری حلقه بزنید. حلقه زدن در جفتهای کلید-مقدار، کلیدها و مقدارهای یک دیکشنری را آموختید. همچنین یاد گرفتید که چگونه چند دیکشنری را درون یک لیست، لیستها را درون یک دیکشنری و دیکشنری را درون یک دیکشنری دیگر قرار دهید.
در فصل بعد دربارهی حلقههای while و نحوهی دریافت اطّلاعات از کاربرانی که با برنامهی شما کار میکنند، خواهید آموخت. این فصل بسیار مهیّج خواهد بود زیرا میآموزید که تمام برنامههای خود را تعاملی کنید: همهی آنها قادر خواهند بود که به ورودی کاربر پاسخ دهند.
7 - ورودی کاربر و حلقههای while
7.1 - تابع input() چگونه کار میکند؟
بیشتر برنامهها برای این نوشته میشوند که مشکل یک کاربر را حل کنند. برای چنین منظوری شما باید برخی اطّلاعات را از کاربر بگیرید. به عنوان یک مثالِ ساده فرض کنید کسی میخواهد بداند که آیا برای رای دادن به سنّ قانونی رسیده یا نه. اگر برنامهای برای پاسخ دادن به این پرسش بنویسید، پیش از ارائهی هر پاسخی باید از سن کاربر اطّلاع داشته باشید. این برنامه باید از کاربر بخواهد که سن خود را وارد کند، برنامه به محض دریافت این ورودی، میتواند آن را با سن رایگیری مقایسه کند تا تعیین کند که آیا سن کاربر مناسب است یا نه و سپس خروجی را گزارش کند.
در این فصل میآموزید که چگونه ورودی کاربر را بپذیرید تا برنامهی شما بتواند با آن کار کند. زمانی که برنامهی شما به یک نام احتیاج دارد، میتواند آن نام را از کاربر درخواست کند. اگر برنامهی شما به لیستی از نامها نیاز داشته باشد، میتواند از کاربر دنبالهای از نامها را بخواهد. برای انجام چنین کاری از تابع input() استفاده خواهید کرد.
همچنین یاد خواهید گرفت که چگونه برنامهها را تا زمانی که کاربر بخواهد در حالِ اجرا نگه دارید، تا آنها بتوانند هر چهقدر اطّلاعات نیاز دارند وارد کنند؛ و برنامهی شما بتواند با این دادهها کار کند. از حلقهی while پایتون برای در حال اجرا نگهداشتنِ برنامهها تا زمانِ برقراریِ شرطهای مشخّصی، استفاده خواهید کرد.
با وجودِ دو قابلیّتِ کار کردن با ورودی کاربر و امکان کنترل مدّتزمان اجرای برنامه، میتوانید برنامههای کاملاً تعاملی بنویسید.
تابع input() چگونه کار میکند؟
تابع input() برنامهی شما را متوقّف میکند و منتظر میماند تا کاربر متنی وارد کند. پایتون به محضِ دریافت ورودیِ کاربر، آن را به متغیّری منسوب میکند تا کار کردن با آن ساده شود.
برای نمونه برنامهی زیر از کاربر میخواهد تا متنی را وارد کند و سپس همان را به کاربر نمایش میدهد:
message = input("Tell me something, and I will repeat it back to you: ")
print(message)
تابع input() تنها یک نشانوند میپذیرد: اعلان یا دستورالعملهایی، که میخواهیم به کاربر نشان دهیم تا بداند چه باید بکند. در این مثال، هنگامی که پایتون خطّ اوّل را اجرا میکند کاربر اعلانِ Tell me something, and I will repeat it back to you را میبیند. برنامه منتظر میماند تا کاربر پاسخ خود را وارد کند و پس از فشرده شدن دکمهی ENTER کار را ادامه میدهد. پاسخ به متغیّر message منسوب میشود و سپس print(message) ورودی را به کاربر برمیگرداند:
Tell me something, and I will repeat it back to you: Hello everyone!
Hello everyone!
نکته
Sublime Text و بسیاری دیگر از ویرایشگرها برنامههایی را که از کاربر ورودی میخواهند، اجرا نمیکنند. میتوانید از این ویرایشگرها برای نوشتن چنین برنامههایی استفاده کنید امّا باید آنها را در یک ترمینال اجرا کنید.
7.2 - نوشتن اعلانهای شفّاف
هر بار که از تابع input() استفاده میکنید باید پیامی شفاّف و قابل درک را ضمیمه کنید تا به کاربر بگوید دقیقاً به دنبال چه اطّلاعاتی هستید. هر عبارتی که به کاربر بگوید که چه باید وارد کند، مناسب است. برای مثال:
name = input("Please enter your name: ")
print(f"\nHello, {name}!")
یک فاصلهی خالی بعد از اعلانهای خود بگذارید (در این مثال بعد از دونقطه) تا اعلان را از پاسخ کاربر جدا کند و محلِّ وارد کردن متن را برای او روشن کند. برای مثال:
گاهی نیاز دارید اعلانی طولانیتر از یک خط بنویسید. مثلاً میخواهید به کاربر بگویید که چرا به دنبال چنین ورودیای هستید. میتوانید اعلان خود را به یک متغیّر نسبت دهید و آن متغیّر را به تابع input() بدهید. با این کار ساختن اعلانهایی در چند خط و سپس نوشتن یک عبارت input() مرتّب، ممکن میشود.
prompt ="If you tell us who you are, we can personalize the messages you see."prompt +="\nWhat is your first name? "name = input(prompt)
print(f"\nHello, {name}!")
این مثال یکی از روشهای ساختن رشتهی چندخطّی را نشان میدهد. خطّ اوّل بخش اوّل پیام را به متغیّر prompt نسبت میدهد. در خطّ دوم عملگر += رشتهی متغیّر prompt را گرفته و رشتهی جدید را به انتهای آن اضافه میکند.
اعلان اکنون دو خط را در برمیگیرد و همچنان برای وضوح بیشتر یک فاصلهی خالی بعد از علامت سوال قرار داده شده است:
If you tell us who you are, we can personalize the messages you see.
What is your first name? Eric
Hello, Eric!
استفاده از int() برای پذیرفتن ورودیهای عددی
هنگامی که از تابع input() استفاده میکنید، پایتون هر آن چه را که کاربر وارد کرده به عنوان رشته تفسیر میکند. این نشستِ مفسّر را در نظر بگیرید که از کاربر سنّ او را میپرسد:
>>> age = input("How old are you? ")How old are you? 21
>>> age
'21'
کاربر عدد ۲۱ را وارد میکند امّا زمانی که از پایتون مقدار age را میخواهیم ‘21’ را برمیگرداند که بیانِ رشتهایِ مقدارِ عددی است که وارد شده. ما میدانیم که پایتون ورودی را به عنوان رشته تفسیر کرده زیرا عدد را داخل علامت نقل قول قرار داده است. اگر چاپ کردنِ ورودی همهی کاری باشد که میخواهید انجام دهید، این کد به درستی کار میکند. امّا اگر بخواهید از آن به عنوان عدد استفاده کنید به خطا میخورید:
>>> age = input("How old are you? ")How old are you? 21
>>> age >=18Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() >= int()
زمانی که در 1 تلاش میکنید ورودی را با عددی مقایسه کنید، پایتون اعلام خطا میکند زیرا نمیتواند رشته را با یک عدد صحیح مقایسه کند: رشتهی ‘21’ که به age منسوب شده قابلِ مقایسه با مقدارِ عددیِ ۱۸ نیست.
این مشکل را میتوان با بهکارگیری تابع int() مرتفع کرد. این تابع از پایتون میخواهد که با ورودی مثلِ یک مقدارِ عددی رفتار کند. تابع int() نمایش رشتهای یک عدد را به نمایش عددیِ آن تبدیل میکند، همانگونه که اینجا دیده میشود:
>>> age = input("How old are you? ")How old are you? 21
>>> age = int(age)>>> age >=18True
در این مثال زمانی که در اعلانِ ورودی ۲۱ را وارد میکنیم، پایتون آن را رشته در نظر میگیرد امّا در ادامه در 1 این مقدار به کمک int()، به نمایش عددی تبدیل میشود. اکنون پایتون میتواند مقایسهی عددی را اجرا کند.
چگونه از int() در یک برنامهی واقعی استفاده کنیم؟ برنامهای را در نظر بگیرید که تعیین میکند آیا قدّ افراد برای سوار شدن به یک ترن هوایی به اندازهی کافی بلند است یا نه:
height = input("How tall are you, in inches? ")
height = int(height)
if height >=48:
print("\nYou're tall enough to ride!")
else:
print("\nYou'll be able to ride when you're a little older.")
این برنامه میتواند height را با ۴۸ مقایسه کند زیرا height = int(height) مقدار ورودی را پیش از انجامِ مقایسه به یک نمایش عددی تبدیل میکند. اگر عددِ واردشده بزرگتر از یا مساوی با ۴۸ باشد به کاربر میگوییم که قد او مناسب است:
عملگر پیمانه حاصلِ تقسیم را، یعنی تعداد باری که یک عدد در عدد دیگر جا میگیرد، برنمیگرداند بلکه تنها میگوید که باقیماندهی تقسیم چیست.
زمانی که عددی بر عدد دیگری بخشپذیر باشد، باقیمانده ۰ است بنابراین عملگر پیمانه عدد صفر را برمیگرداند. میتوانید از این حقیقت برای تعیین زوج یا فرد بودنِ عددی استفاده کنید:
number = input("Enter a number, and I'll tell you if it's even or odd: ")
number = int(number)
if number %2==0:
print(f"\nThe number {number} is even.")
else:
print(f"\nThe number {number} is odd.")
اعدادِ زوج همواره برای ۲ بخشپذیرند پس اگر باقیماندهی تقسیم یک عدد بر ۲، ۰ باشد (در اینجا if number % 2 == 0) آن عدد زوج است. در غیر این صورت فرد خواهد بود:
Enter a number, and I'll tell you if it's even or odd: 42
The number 42 is even.
خودآزمایی
برنامهی ماشین کرایهای
برنامهای بنویسید که از کاربر بپرسد چهجور ماشین کرایهای دوست دارد. پیامی دربارهی آن ماشین چاپ کنید، مثلاً: “Let me see if I can find you a Subaru.”.
جاگرفتن در رستوران
برنامهای بنویسید که از کاربر بپرسد چند نفر در گروه شام او حضور دارد. اگر پاسخ بیش از هشت بود، پیامی چاپ کند که باید منتظر خالی شدن یک میز بمانند. در غیر این صورت بگوید که میز آنها حاضر است.
مضربهای ده
از کاربر عددی بپرسید و سپس گزارش دهید که آیا آن عدد مضربی از ۱۰ است یا نه؟
7.4 - آشنایی با حلقههای while
حلقهی for مجموعهای را میگیرد و قطعهکدی را یک بار برای هر عضو آن اجرا میکند. در مقابل، حلقهی while تا زمانی که شرط خاصی درست باشد، اجرا میشود.
حلقهی while در عمل
میتوانید از یک حلقهی while برای شمردن دنبالهای از اعداد استفاده کنید. برای مثال حلقهی while زیر از ۱ تا ۵ میشمرد:
در خطّ اوّل شمردن از ۱ را با انتساب مقدار ۱ به متغیّر current_number شروع میکنیم. سپس حلقهی while طوری تنظیم میشود که تا زمانِ کمتر یا مساوی بودنِ current_number از ۵، اجرا شود. کدِ داخلِ حلقه مقدارِ current_number را چاپ کرده و سپس با += آن را یکی زیاد میکند. (عملگر += کوتاهشدهی current_number = current_number + 1 است.)
پایتون تا زمانی حلقه را اجرا میکند که شرطِ current_number <= 5 برقرار باشد. پایتون ۱ را چاپ میکند سپس یکی به آن اضافه میکند تا عدد فعلی ۲ شود. چون ۲ کمتر از ۵ است، آن را چاپ کرده و مجدّداً یکی به آن اضافه میکند تا عدد فعلی ۳ شود و به همین ترتیب ادامه میدهد. به محض بیشتر شدنِ مقدارِ current_number از ۵، اجرای حلقه متوقّف شده و برنامه پایان مییابد:
بیشتر برنامههایی که هرروزه استفاده میکنید شامل حلقههای whileاند. برای مثال، یک بازی به حلقهی while احتیاج دارد که تا زمانی که میخواهید به بازی ادامه دهید، به اجراشدن ادامه دهد و به محض رسیدنِ درخواستِ خروج اجرا شدن را متوقّف کند.
اجازه دادن به کاربر برای انتخاب زمان خروج
برای این که برنامهی parrot.py تا زمانی که کاربر میخواهد، اجرا شود میتوانیم بیشتر خطهای آن را در یک حلقهی while قرار دهیم. یک مقدارِ خروج تعریف میکنیم و تا زمانی که کاربر آن را وارد نکرده باشد، اجرای برنامه ادامه مییابد:
prompt ="\nTell me something, and I will repeat it back to you:"prompt +="\nEnter 'quit' to end the program. "message =""while message !='quit':
message = input(prompt)
print(message)
در 1 اعلانی تعریف میکنیم تا به کاربران بگوییم که دو گزینه پیشِ رو دارند: وارد کردن یک پیام یا وارد کردن مقدارِ خروج (در اینجا، ‘quit’). سپس در 2 متغیّر message را مقداردهی میکنیم تا هر مقداری را که کاربر وارد میکند، پیگیری کنیم. متغیّر message را یک رشتهی خالی "" قرار میدهیم تا پایتون در اوّلین اجرای حلقه چیزی برای مقایسه کردن داشته باشد. حلقهی while (3) تا زمانی که مقدار message برابر با ‘quit’ نباشد اجرا میشود.
در اوّلین ورود به حلقه، message تنها یک رشتهی خالی است، پس پایتون میتواند وارد حلقه شود. در message = input(prompt) پایتون اعلان را نمایش میدهد و منتظر پاسخ کاربران میماند. هر آنچه وارد کنند به message نسبت داده شده و چاپ میشود؛ سپس پایتون شرطِ عبارتِ while را دوباره ارزیابی میکند. سرانجام زمانی که کاربران ‘quit’ را وارد میکنند، پایتون اجرای حلقهی while را متوقّف کرده و برنامه پایان مییابد:
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. Hello everyone!
Hello everyone!
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. Hello again.
Hello again.
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. quit
quit
این برنامه به خوبی کار میکند، تنها ایراد آن این است که واژهی ‘quit’ را مثل یک پیام واقعی چاپ میکند. یک آزمون if ساده این مشکل را حل میکند:
prompt ="\nTell me something, and I will repeat it back to you:"prompt +="\nEnter 'quit' to end the program. "message =""while message !='quit':
message = input(prompt)
if message !='quit':
print(message)
اکنون برنامه پیش از نمایش پیام یک بررسی سریع انجام میدهد و تنها در صورتی آن را چاپ میکند که با مقدارِ خروج برابر نباشد:
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. Hello everyone!
Hello everything!
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. Hello again.
Hello again.
Tell me something, and I will repeat it back to you:
Enter 'quit' to end the program. quit
7.5 - بهکارگیری پرچم
در مثالِ پیشین برنامه را واداشتیم که تا زمانِ برقرار بودنِ شرطی مشخّص، به انجام وظایفی بپردازد. امّا در برنامههای پیچیده که درآنها رخدادهای مختلفی ممکن است باعث توقّف اجرا شوند، چه باید کرد؟
برای مثال در یک بازی رخدادهای مختلف و فراوانی میتوانند به بازی خاتمه دهند. وقتی تمام کشتیهای کاربر غرق میشود، زمان او به پایان میرسد یا شهرهایی که باید از آنها محافظت میکرده همگی نابوده شدهاند، بازی باید پایان یابد. بازی باید با رخ دادن هر کدام از این رویدادها به پایان برسد. اگر امکان وقوع رخدادهای مختلفی برای پایان دادن به برنامه وجود دارد، آزمودن همهی آنها در یک عبارت while ممکن است پیچیده و مشکل باشد.
در برنامهای که فقط باید تا زمان برقراری شرطهایی اجرا شود، میتوانید یک متغیّر تعریف کنید که تعیین کند کل برنامه فعّال است یا نه. این متغیّر که به آن پرچم گویند، در برنامه مانند یک نشانه عمل میکند. میتوانیم برنامه را به نحوی بنویسیم که تا زمان درست (True) بودن پرچم اجرا شود و هنگامی که یکی از چندین رخداد مقدار آن را نادرست (False) کند، اجرای برنامه متوقّف شود.
بیایید پرچمی به parrot.py از بخش قبل، اضافه کنیم. این پرچم که آن را active مینامیم (و هر نامی که بخواهید میتوانید به آن بدهید) نگاه میکند که آیا اجرای برنامه باید ادامه یابد یا نه:
prompt ="\nTell me something, and I will repeat it back to you:"prompt +="\nEnter 'quit' to end the program. "active =Truewhile active:
message = input(prompt)
if message =='quit':
active =Falseelse:
print(message)
در 1 متغیّر active را برابر True میگذاریم تا برنامه در وضعیّت فعّال قرار گیرد. انجام چنین کاری عبارت while را سادهتر میکند زیرا هیچ مقایسهای در خود عبارت صورت نمیپذیرد؛ منطق برنامه در دیگر بخشهای آن مورد نظر قرار میگیرد. تا زمانی که متغیّر active مقدار True دارد اجرای حلقه ادامه مییابد (2).
به محض این که کاربران ورودی خود را اعمال کنند مقدار message را در گزارهی if داخل حلقهی while بررسی میکنیم. اگر در 3 کاربر ‘quit’ را وارد کرد، active را False میکنیم و حلقهی while متوقّف میشود. اگر کاربر چیزی جز ‘quit’ وارد کند، 4، ورودی او را در قالب پیامی چاپ میکنیم.
خروجی این برنامه مشابه خروجی مثال قبلی است. حال که ما پرچمی داریم که نشان میدهد کل برنامه در وضعیّت فعّال است یا نه، افزودن آزمونهای بیشتر (مثلاً با عبارتهای elif) برای رخدادهایی که باید active را False کنند، ساده خواهد بود.
7.6 - استفاده از break و continue
استفاده از break برای خروج از حلقه
برای این که بدون اجرای کدهای باقیمانده در حلقهی while و صرفِ نظر از نتایج هر آزمون شرطیای، فوراً از آن خارج شوید از break استفاده کنید. عبارت break جریان برنامهی شما را هدایت میکند؛ میتوانید از آن برای مدیریّت اجرا شدن یا نشدن خطهایی از کد استفاده کنید.
برای نمونه برنامهای را در نظر بگیرید که از کاربران میپرسد از چه مکانهایی دیدن کردهاند. در این برنامه به محضی که کاربر مقدار ‘quit’ را وارد میکند، حلقهی while را با فراخوانی break متوقّف میکنیم:
prompt ="\nPlease enter the name of a city you have visited:"prompt +="\n(Enter 'quit' when you are finished.) "whileTrue:
city = input(prompt)
if city =='quit':
breakelse:
print(f"I'd love to go to {city.title()}!")
حلقهای که مانند 1 با while True آغاز شود تا ابد اجرا میشود مگر این که به عبارت break برسد. پرسیدنِ نام شهرهایی که کاربران در آنها بودهاند تا زمانی که ‘quit’ را وارد کنند در حلقهی این برنامه ادامه مییابد. هنگامی که کاربران ‘quit’ را وارد میکنند عبارت break اجرا میشود که باعث میشود پایتون از حلقه خارج شود:
Please enter the name of a city you have visited:
(Enter 'quit' when you are finished.) New York
I'd love to go to New York!
Please enter the name of a city you have visited:
(Enter 'quit' when you are finished.) San Francisco
I'd love to go to San Francisco!
Please enter the name of a city you have visited:
(Enter 'quit' when you are finished.) quit
نکته
میتوانید از break در تمام حلقههای پایتون استفاده کنید. برای مثال با بهکارگیری break میتوانید از حلقهی forای که داخل یک لیست یا دیکشنری در حال کار کردن است، خارج شوید.
بهکارگیری continue در یک حلقه
به جای این که بدونِ اجرای باقیماندهی کد، از حلقهای به کلّی خارج شوید، میتوانید از عبارت continue استفاده کنید تا بر اساس نتیجهی یک آزمون شرطی به ابتدای حلقه برگردید. برای نمونه حلقهای را در نظر بگیرید که از ۱ تا ۱۰ میشمرد امّا تنها اعداد فردِ این بازه را چاپ میکند:
ابتدا مقدار current_number را برابر با ۰ میگذاریم. چون این مقدار کمتر از ۱۰ است پایتون وارد حلقه میشود. با ورود به حلقه در 1 مقدار را یکی افزایش میدهیم، پس current_number برابر با ۱ خواهد بود. سپس گزارهی if باقیماندهی current_number بر ۲ را بررسی میکند. اگر باقیمانده ۰ باشد (یعنی current_number بر ۲ بخشپذیر باشد)، عبارت continue از پایتون میخواهد که باقیِ حلقهی را نادیده بگیرد و به ابتدای آن برگردد. اگر عدد فعلی برای ۲ بخشپذیر نباشد ادامهی حلقه اجرا شده و پایتون آن را چاپ میکند:
# This loop runs forever!x =1while x <=5:
print(x)
اکنون مقدار x از ۱ شروع میشود امّا هرگز تغییر نمیکند. در نتیجه آزمون شرطی x <= 5 همواره True ارزیابی میشود و حلقهی while برای همیشه اجرا شده و دنبالهای از ۱ها چاپ میکند:
هر برنامهنویسی گاه به گاه و به صورت اتّفاقی حلقهی while بیپایان مینویسد، به ویژه زمانی که حلقههای برنامه شرطهای خروج ظریف و دیرفهمی دارند. اگر برنامهی شما در یک حلقهی بیپایان گیر کرد CTRL-C را فشار دهید یا به سادگی پنجرهی ترمینالی را که خروجی برنامه را نشان میدهد ببندید.
برای پرهیز از نوشتن حلقههای بیپایان، هر حلقهی while را بیازمایید و مطمئن شوید اجرای آن در زمانی که شما انتظار دارید متوقّف میشود. اگر از برنامه میخواهید که با دریافت یک ورودی مشخّص پایان یابد، آن را اجرا کنید و آن مقدار را وارد کنید. اگر برنامه پایان نیافت، روش برنامه را پایان دادنِ به حلقه، موشکافانه مورد بررسی قرار دهید.
نکته
Sublime Text و برخی دیگر از ویرایشگرها یک پنجرهی خروجی درونی دارند که ممکن است متوقّف کردن یک حلقهی بیپایان را مشکل کند و شما را مجبور کند که خود ویرایشگر را برای پایان دادن به حلقه ببندید. سعی کنید پیش از فشردن CTRL+C روی ناحیهی پنجرهی خروجی کلیک کنید تا قادر باشید حلقهی بیپایان را متوقّف کنید.
خودآزمایی
مواد روی پیتزا
حلقهای بنویسید که از کاربران بخواهد پیش از وارد کردن مقدار ‘quit’، مجموعهای از مواد روی پیتزا را وارد کنند. همزمان که موارد را وارد میکنند، پیامی چاپ کنید که میگوید شما آن مورد را به پیتزا اضافه خواهید کرد.
بلیتهای سینما
یک سالن سینما قیمت بلیتها را بر اساس سنّ فرد تعیین میکند. اگر فرد کمتر از ۳ سال سن داشته باشد، بلیت او رایگان است؛ اگر بین ۳ تا ۱۲ سال داشته باشند باید ۱۰ دلار بپردازند و اگر بیش از ۱۲ سال سن داشته باشند بلیت آنها ۱۵ دلار خواهد بود. حلقهای بنویسید که در آن سن کاربران پرسیده شده و سپس قیمت بلیت آنها اعلام میشود.
سه خروجی
نسخههای متفاوتی از تمرین ۷-۴ یا ۷-۵ بنویسید که کارهای زیر را حدّاقل یک بار انجام دهد:
در عبارت while یک آزمون شرطی بیاورید که اجرای حلقه را متوقّف کند.
از یک متغیّر فعّال بودن استفاده کنید تا مدّت زمان اجرای حلقه را کنترل کنید.
هنگامی که کاربر مقدار ‘quit’ را وارد میکند، از یک عبارت break برای خروج از حلقه استفاده کنید.
بیپایانی
حلقهای بنویسید که هیچگاه پایان نمییابد و آن را اجرا کنید. (برای پایان دادن به این حلقه CTRL+C را فشار دهید یا پنجرهای که خروجی را نشان میدهد ببندید.)
7.8 - بهکارگیری حلقهی while در لیستها و دیکشنریها
تا اینجا هر بار تنها با یک مورد از اطّلاعات کاربر سر و کار داشتهایم. ورودی را از کاربر گرفته و همان را یا پاسخی به آن را چاپ کردهایم. پس از آن در یک حلقهی while مقدار ورودیِ دیگری را گرفتیم و به آن پاسخ دادیم. امّا برای نگهداری و پیگیری کاربران بیشتر یا اطّلاعات پرتعدادتر در حلقههای while، به لیستها و دیکشنریها احتیاج داریم.
بهکارگیری یک حلقهی for برای حلقه زدن در یک لیست موثّر است امّا نباید لیست را در داخل این حلقه تغییر داد چون پایتون در پیگیری موارد داخل لیست دچار مشکل میشود. اگر میخواهید هنگام کار کردن با یک لیست آن را تغییر دهید، از حلقهی while استفاده کنید.
انتقال دادن اعضا از یک لیست به لیستِ دیگر
لیستی از کاربران تازه ثبتنام شدهی یک وبسایت را در نظر بگیرید که هنوز تایید نشدهاند. چگونه میتوانیم پس از تایید این کاربران، آنها را به لیستی مجزّا شامل کاربران تاییدشده منتقل کنیم؟ یک روش استفاده کردن از حلقهی while است تا به کمک آن کاربران را از لیست کاربران تاییدنشده بیرون بکشیم، تایید کنیم و سپس به لیستی جداگانه از کاربران تاییدشده اضافه کنیم:
# Start with users that need to be verified,# and an empty list to hold confirmed users.unconfirmed_users = ['alice', 'brian', 'candace']
confirmed_users = []
# Verify each user until there are no more unconfirmed users.# Move each verified user into the list of confirmed users.while unconfirmed_users:
current_user = unconfirmed_users.pop()
print(f"Verifying user: {current_user.title()}")
confirmed_users.append(current_user)
# Display all confirmed users.print("\nThe following users have been confirmed:")
for confirmed_user in confirmed_users:
print(confirmed_user.title())
در 1 با لیستِ کاربرانِ تاییدنشده (Alice، Brian و Candace) و لیستی خالی برای نگهداری کاربران تاییدشده شروع میکنیم. حلقهی while در 2 تا زمانی که لیست unconfirmed_users خالی نباشد به کار ادامه میدهد. داخل این حلقه و در 3، تابع pop() هر بار کاربران تاییدنشده را از انتهای unconfirmed_users برمیدارد. اینجا چون Candace آخرین کاربر در unconfirmed_users است، نامش اوّلین نامی خواهد بود که از لیست حذف، به متغیّر current_user منسوب و به لیست confirmed_users اضافه میشود (4). بعدی Brian و سپس Alice خواهد بود.
فرآیند تایید کاربران را با چاپ کردن یک پیام تایید شبیهسازی میکنیم و سپس کاربران را به لیست تاییدشدهها میافزاییم. همزمان با کوچک شدن لیست کاربران تاییدنشده، لیست تاییدشدهها بزرگتر میشود:
Verifying user: Candace
Verifying user: Brian
Verifying user: Alice
The following users have been confirmed:
Candace
Brian
Alice
حذف کردن مقدارهای مشخّصی از یک لیست
در فصل ۳ از remove() برای حذف کردن یک مقدار مشخّص از لیست استفاده کردیم. این تابع به این دلیل کار میکرد که مقدار مورد نظر ما تنها یک بار در لیست ظاهر شده بود. امّا اگر بخواهیم تمام نمونههای یک مقدار را از لیستی حذف کنیم، چه؟
فرض کنید لیستی از حیوانات خانگی دارید که ‘cat’ در آن بارها تکرار شده است. برای حذف تمام مشابههای این مقدار میتوانید یک حلقهی while را تا زمانی اجرا کنید که هیچ موردی از ‘cat’ در لیست باقی نمانده باشد:
با لیستی شامل چند عضو مشابه ‘cat’ شروع میکنیم. پس از چاپ کردن این لیست، پایتون وارد حلقهی while میشود زیرا مقدار ‘cat’ را دستِ کم یک بار در لیست مییابد. پس از ورود به حلقه، پایتون اوّلین نمونهی ‘cat’ را حذف کرده و به خطّ while برمیگردد. سپس از آنجا که هنوز ‘cat’ در لیست وجود دارد، مجدّداً وارد حلقه میشود. تا زمانی که دیگر هیچ مقداری مشابه ‘cat’ در حلقه موجود نباشد، هر نمونهی آن را حذف میکند:
در هر مرحلهی اجرای حلقهی while، میتوانید هرقدر اطّلاعات که بخواهید دریافت کنید. بیایید یک برنامهی نظرسنجی بسازیم که در آن در هر بار اجرای حلقه یک بار نام و نظر مشارکتکنندگان را میپرسیم. دادههای گردآوریشده را در یک دیکشنری ذخیره میکنیم زیرا میخواهیم بین هر کاربر و پاسخ او ارتباط برقرار کنیم:
responses = {}
# Set a flag to indicate that polling is active.polling_active =Truewhile polling_active:
# Prompt for the person's name and response. name = input("\nWhat is your name? ")
response = input("Which mountain would you like to climb someday? ")
# Store the response in the dictionary. responses[name] = response
# Find out if anyone else is going to take the poll. repeat = input("Would you like to let another person respond? (yes/ no) ")
if repeat =='no':
polling_active =False# Polling is complete. Show the results.print("\n--- Poll Results ---")
for name, response in responses.items():
print(f"{name} would like to climb {response}.")
این برنامه ابتدا یک دیکشنری خالی (responses) تعریف میکند و پرچمی (polling_active) را برای نمایش فعّال بودن نظرسنجی مقداردهی میکند. تا زمانی که polling_active برابر با True باشد، پایتون کد داخل حلقهی while را اجرا میکند.
در داخل حلقه به کاربر اعلام میشود که نام و کوه مورد علاقهی خود برای نوردیدن را وارد کند. این اطّلاعات در دیکشنری responses ذخیره میشود و از کاربر پرسیده میشود که آیا نظرسنجی ادامه یابد یا نه. اگر yes وارد شود، برنامه بار دیگر وارد حلقهی while میشود. اگر no وارد شود پرچم polling_active به False تغییر مییابد و اجرای حلقهی while متوقّف میشود. سرانجام بخش آخر کد نتایج نظرسنجی را نشان میدهد.
اگر برنامه را اجرا کرده و چند پاسخ نمونه وارد کنید، باید خروجیای مانند زیر ببینید:
What is your name? Eric
Which mountain would you like to climb someday? Denali
Would you like to let another person respond? (yes/ no) yes
What is your name? Lynn
Which mountain would you like to climb someday? Devil's Thumb
Would you like to let another person respond? (yes/ no) no
--- Poll Results ---
Lynn would like to climb Devil's Thumb.
Eric would like to climb Denali.
خودآزمایی
غذافروشی
لیستی به نام sandwich_orders بسازید و آن را با نام چند نوع ساندویچ پر کنید. سپس لیستی خالی به نام finished_sandwiches بسازید. در لیست سفارشهای ساندویچ حلقه بزنید و برای هر کدام پیامی چاپ کنید، مثلاً I made your tuna sandwich.. با درست شدن هر ساندویچ، سفارش آن را به لیستهای ساندویچهای تحویلدادهشده منتقل کنید. پس از آماده شدن تمام ساندویچها پیامی چاپ کنید و تمام ساندویچهای آمادهشده را فهرست کنید.
تمام شدنِ پاسترامی
لیستِ sandwich_orders در تمرین ۷-۸ را دوباره به کار بگیرید و مطمئن شوید ساندویچ ‘pastrami’ دستِ کم ۳ بار در آن تکرار شده باشد. در ابتدای برنامهی خود پیامی چاپ کنید حاکی از آنکه موجودیِ پاسترامی در این غذاخوری به پایان رسیده است، سپس به کمک یک حلقهی while تمام موارد ‘pastrami’ را از sandwich_orders حذف کنید. مطمئن شوید که در لیست ساندویچهای آماده هیچ نمونهای از ساندویچ پاسترامی نباشد.
سفر رویایی
برنامهای بنویسید که از کاربران دربارهی سفر رویایی آنها نظرسنجی کند. اعلانی مانند این چاپ کنید: If you could visit one place in the world, where would you go?. بخشی از کد را به چاپ کردن نتایج نظرسنجی اختصاص دهید.
7.9 - خلاصه
در این فصل آموختید که چگونه input() را به کار بگیرید تا امکان وارد کردن اطّلاعات را برای کاربران فراهم کنید. یاد گرفتید که چگونه با هر دو نوع ورودی رشته و عددی کار کنید و با حلقههای while اجرای برنامههای خود را تا زمانی که کاربر بخواهد ادامه دهید. چندین روش را برای کنترل کردن جریان یک حلقهی while آموختید، مانند تعریف یک پرچم active، استفاده از عبارتهای break و continue. یاد گرفتید که چگونه برای انتقال اعضای یک لیست به لیستی دیگر و همچنین حذفِ تمامِ موارد مشابه با یک مقدار از یک لیست، از حلقهی while استفاده کنید. همچنین دانستید که میتوان حلقههای while را با دیکشنریها هم استفاده کرد.
در فصل ۸ در مورد تابعها خواهید آموخت. توابع به شما امکان میدهند که برنامهها را به قسمتهای کوچکتر بشکنید که هر قسمت کار خاصی انجام دهد. میتوانید یک تابع را هر چند بار که خواستید فراخوانی و آنها را در فایلهای جداگانه ذخیره کنید. با بهکارگیری تابعها میتوانید کد کاراتری بنویسید که عیبیابی آن سادهتر است و میتوان آنها را در برنامههای مختلف دوباره استفاده کرد.
8 - تابعها
8.1 - تعریف کردن تابع
در این فصل نوشتنِ تابعها را یاد میگیرید. توابع قسمتهای نامگذاریشدهی کد هستند که برای انجام کار خاصّی طرّاحی میشوند. وقتی میخواهید وظیفهی خاصّی را که در یک تابع تعریف شده انجام دهید، تابعی را که عهدهدار آن است فراخوانی میکنید. اگ 66
ر به انجامِ چندبارهی کاری نیاز داشته باشید، نیازی نیست کد آن را بارها تکرار کنید، کافی است تابعی را که به مدیریّت این کار اختصاص دادهاید فراخوانی کنید و این فراخوانی موجب میشود که پایتون کد داخل تابع را اجرا کند. شما درخواهید یافت که بهکارگیری تابعها نوشتن، خواندن، آزمایش و رفع عیب برنامههای شما را آسانتر میکند.
در این فصل راههای انتقال اطّلاعات به تابعها را ه67
م میآموزید و یاد میگیرید که چهگونه تابعهای خاصی را که وظیفهی اصلی آنها نمایش اطّلاعات است، و توابع دیگری را که برای پردازش دادهها و بازگرداندن یک مقدار یا مجموعهای از مقدارها طرّاحی شدهاند، بنویسید. در نهایت ذخیره کردن تابعها را در فایلهای جداگانهای که ماژول نامیده میشوند میآموزید که به سازماندهی فایلهای اصلی برنامه کمک میکنند.
اینجا تابعی ساده به نام greet_user() میبینید که یک پیام احوالپرسی را چاپ میکند:
defgreet_user():
"""Display a simple greeting.""" print("Hello!")
greet_user()
این مثال سادهترین ساختار یک تابع 68
را نشان میدهد. خط ۱ از کلیدواژهی def استفاده میکند تا پایتون را مطّلع کند که شما میخواهید یک تابع تعریف کنید. این تعریف تابع است که به پایتون میگوید نام آن چیست و برای به کار بردن با آن به چه اطّلاعاتی نیاز است. پرانتزها این اطّلاعات را نگه میدارند. در این مورد نام تابع greet_user() است و چون برای انجام وظیفهاش به هیچ دانستهای نیاز ندارد، پرانتزهای آن خالی مانده است. (با این حال حتّی در این حالت نیز وجود پرانتزها الزامی است) سرانجام تعریف با یک دونقطه پایان مییابد.
هر خطّ تورفتهای که پس از def greet_user(): بیاید بدنهی تابع را تشکیل میدهد. متنِ ۲ نوعی توضیح است که docstring نامیده میشود و عملکرد تابع را شرح میدهد. Docstringها در علامتهای نقل قول سهتایی قرار میگیرند و پایتون هنگام تولید مستندات برای تابعهای موجود در برنامهی شما به دنبال آنها میگردد.
خط print("Hello!") در ۳ تنها خطِ برنامه در بدنهی این تابع است، بنابراین greet_user() تنها یک وظیفه دارد: print("Hello").
زمانی که میخواهید از این تابع استفاده کنید، آن را فراخوانی میکنید. فراخوانی تابع از پایتون میخواهد که کد داخل آن را اجرا کند. برای فراخوانی یک تابع نام آن را مینویسید و پس از آن اطّلاعات ضروری را در پرانتز بعد از آن میآورید، چنانکه در ۴ نشان داده شده است. چون اینجا به هیچ اطّلاعاتی نیاز نیست، فراخوانی تابع ما به سادگی و تنها با وارد کردن greet_user() انجام میپذیرد. همانطور که انتظار داشتیم، این تابع Hello! را چاپ میکند:
با کمی تغییر، تابعِ greet_user() نه تنها به کاربر سلام میکند، بلکه میتواند نام او را هم ذکر کند. برای این که تابع چنین کند username را در پرانتزهای تعریفِ تابع در def greet_user() مینویسید. با افزودن username به آنجا، تابع را قادر میسازید که هر مقدار usernameای که تعیین میکنید، بپذیرد. اکنون پایتون انتظار دارد که در هر بار فراخوانیِ تابع، مقداری برای username ارائه دهید. هنگامی که greet_user() را فرامیخوانید میتوانید در پرانتزهای آن، نامی مانند 'jesse' را به این تابع بدهید:
defgreet_user(username):
"""Display a simple greeting.""" print(f"Hello, {username.title()}!")
greet_user('jesse')
وارد کردن greet_user('jesse') تابع greet_user() را فراخوانی کرده و به آن اطّلاعات مورد نیاز را برای اجرا کردن تابع print() ارائه میدهد. این تابع نامی را که به آن دادهاید گرفته و پیامی خطاب به او نمایش میدهد:
مشابه این، وارد کردن greet_user('sarah') تابع greet_user() را فراخوانده، 'sarah' را به آن منتقل کرده و Hello, Sarah! را چاپ میکند. میتوانید greet_user() را هر زمان که بخواهید فرابخوانید و هر نامی را که خواستید به آن بدهید تا هر بار یک خروجی قابل پیشبینی تولید کند.
پارامترها و نشانوندها
تابع greet_user() قبلی را طوری تعریف کردیم که مقداری برای متغیّر username درخواست کند. با فراخوانی آن و ارائهی اطّلاعاتی که نیاز دارد (نام فرد)، این تابع پیام مناسب را چاپ میکرد.
متغیّر username در تعریفِ تابع greet_user() نمونهای از یک پارامتر 70 است، اطّلاعاتی که تابع برای انجام کار خود نیاز دارد. مقدار 'jesse' در greet_user('jesse') نمونهای از نشانوند یا آرگومان است. نشانون 71
د اطّلاعاتی است که هنگام فراخوانی یک تابع، به آن ارائه میشود. زمانی که تابع را فراخوانی میکنیم، مقدار مورد نظر خود را در داخل پرانتزهای آن قرار میدهیم. در این مورد نشانوند 'jesse' به تابع greet_user() منتقل شده و این مقدار به پارامتر username منسوب شده است.
نکته
افراد گاهی نشانوند و پارامتر را به جای هم به کار میبرند. اگر جایی دیدید که به متغیّرهای تعریفشده در تعریفِ تابع نشانوند یا به متغیرّهای فراخوانی تابع پارامتر گفتهاند، تعجّب نکنید.
خودآزمایی
۸-۱. پیام: تابعی به نام display_message() بنویسید که جملهای چاپ کند مبنی بر این که شما در این فصل مشغولِ آموختنِ چه هستید. تابع را فراخوانی کنید و مطمئن شوید که پیام به درستی نمایش داده میشود.
۸-۲. کتاب دلخواه: تابعی به نام favorite_book() بنویسید که یک پارامتر به نام title بپذیرد. این تابع باید پیامی چاپ کند مانند One of my favorite books is Alice in Wonderland. تابع را فراخوانی کنید و مطمئن شوید که هنگام فراخوانی، عنوانِ کتابی را به عنوانِ نشانوند به آن میدهید.
8.3 - انتقال نشانوندها
فراخوانی یک تابع ممکن است به نشانونن73
یاز داشته باشد زیرا تعریف تابع ممکن است شامل چند پارامتر باشد. چند راه برای انتقال نشانوندها به تابعها دارید. میتوانید از نشانوندهای مکاندار استفاده کنید که هنگام انتقال به تابع باید با همان ترتیبِ تعریف شدهِ پارامترها وارد شوند. در نشانوندهای کلیدواژهای هر نشانوند از نام متغیّر و یک مقدار، لیست یا دیکشنریهایی از مقدارها تشکیل شده است. در ادامه به هر یک از این دو مورد خواهیم پرداخت.
نشانوندهای مکاندار
هنگام فراخوانی یک تابع، پایتون باید هر نشانوند را با پارامتر متناظرش در تعریفِ تابع، جفت کند. سادهترین راه، انجامِ این کار بر اساسِ ترتیبِ تعیینِ نشانوندهاست. مقدارهایی که به این ترتیب جفت میشوند نشانوندهای مکاندار نامیده میشوند.
برای این که نحوهی این کار را ببینید تابعی را در نظر بگیرید که اطّلاعاتی را در مورد حیوانات خانگی چاپ میکند. این تابع میگوید که هر حیوان خانگی چه نوع جانوری است و نام آن چیست:
defdescribe_pet(animal_type, pet_name):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
تعریف تابع نشان میدهد که به نوع و نام حیوان نیاز دارد (۱). زمانی که describe_pet() را فراخوانی میکنیم، باید دو مقدار برای نوع و نام حیوان را به همان ترتیب ارائه کنیم. برای نمونه در این فراخوانی تابع، نشانوند 'hamster' به پارامتر animal_type و نشانوند 'harry' به پارامتر pet_name منسوب میشود. (۲) در بدنهی تابع این دو پارامتر برای نمایش اطّلاعاتی در مورد حیوان مورد بحث ارائه میشود.
defdescribe_pet(animal_type, pet_name):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('hamster', 'harry')
describe_pet('dog', 'willie')
در دومین فراخوانیِ تابع دو نشانوندِ 'dog' و 'willie' را به describe_pet() دادیم. مشابه نشانوندهایی که پیش از این به کار بردیم، پایتون 'dog' را با پارامتر animal_type و 'willie' را با پارامتر pet_name تطبیق میدهد.
تابع همانند گذشته کار خود را انجام میدهد امّا این بار مقدارهایی را برای سگی به نام ویلی چاپ میکند. اکنون ما همستری به نام هری و سگی به نام ویلی داریم:
I have a hamster.
My hamster's name is Harry.
I have a dog.
My dog's name is Willie.
فراخواندن چندبارهی یک تابع روشی بسیار موثّر است. کدی که حیوان خانگی را توصیف میکند تنها یک بار در تابع نوشته شده است. پس از آن هر بار که بخواهید حیوانی جدید را توصیف کنید، تابع را با اطّلاعات آن فراخوانی میکنید. حتّی اگر کدِ توصیفِ حیوان خانگی ده خط هم باشد، همچنان میتوانید یک حیوان خانگی جدید را تنها با یک خطْ فراخوانیِ تابع توصیف کنید.
میتوانید هر تعداد نشانوند مکاندار که نیاز داشته باشید در یک تابع استفاده کنید. پایتون نشانوندهایی را که ارائه کردهاید یکی یکی بررسی میکند و هر کدام را با پارامتر متناظر آن در تعریفِ تابع تطبیق میدهد.
در نشانوندهای مکاندار ترتیب اهمّیّت دارد
اگر هنگام بهکارگیری نشانوندهای مکاندار ترتیب آنها را به هم بریزید ممکن است به نتایج ناخواستهای برسید:
defdescribe_pet(animal_type, pet_name):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet('harry', 'hamster')
در این فراخوانیِ تابع نام را اول و نوع حیوان را پس از آن وارد کردهایم. چون نشانوند 'harry' در ابتدا آمده است، مقدار آن به پارامتر animal_type منسوب میشود. به طور مشابه، 'hamster' به pet_name منسوب میشود. اکنون یک هری با نام همستر داریم:
اگر به نتایج خندهداری مثل این رسیدید، بررسی کنید تا مطمئن شوید ترتیب نشانوندها هنگام فراخوانی تابع، به ترتیبِ پارامترها در تعریفِ تابع میخورد.
نشانوندهای کلیدواژهای
نشانوند کلیدواژهای جفتِ نام-مقداری است که به یک تابع منتقل میکنید. شما نام و مقدار را مستقیماً در نشانوند تعیین میکنید درنتیجه هنگام انتقال نشانوند به تابع هیچ گاه سردرگم نخواهید شد. (مانند مثال قبل به خروجیِ هری به نام همستر برنخواهید خورد). نشانوندهای کلیدواژهای، نگرانیِ شما را در مورد حفظ ترتیب درستِ نشانوندها هنگام فراخوانی یک تابع از بین میبرد و نقش هر مقدار را در فراخوانی تابع مشخّص و واضح میکند.
اکنون pets.py را با فراخوانی describe_pet() به کمک نشانوندهای کلیدواژهای بازنویسی میکنیم:
defdescribe_pet(animal_type, pet_name):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(animal_type='hamster', pet_name='harry')
تابع describe_pet() تغییری نکرده است. امّا هنگام فراخوانی آن به طور صریح به پایتون میگوییم که کدام نشانوند باید با کدام پارامتر جفت شود. پایتون فراخوانیِ تابع را میخواند و میفهمد که نشانوند 'hamster' را به پارامتر animal_type و 'harry' را به pet_name نسبت دهد.
ترتیبِ نشانوندهای کلیدواژهای اهمیتی ندارد چون پایتون میداند که هر مقدار باید به کجا برود. این دو فراخوانی تابع یکساناند:
اگر از نشانوندهای کلیدواژهای استفاده میکنید مطمئن شوید که در تعریفِ تابع نام دقیق پارامترها را وارد کنید.
مقدارهای پیشفرض 75
میتوانید هنگام نوشتن یک تابع برای هر پارامتر آن یک مقدار پیشفرض تعیین کنید. اگر هنگام فراخوانیِ تابع برای یک نشانوند، پارامتری ارائه شود پایتون از مقدار آن استفاده میکند. اگر نه، مقدارِ پیشفرضِ پارامتر به کار گرفته میشود. بنابراین اگر برای یک پارامتر مقداری پیشفرض تعیین کنید، ارائهی نشانوندِ متناظر هنگام فراخوانیِ تابع، الزامی نخواهد بود. بهکارگیری مقدارهای پیشفرض فراخوانی توابع را سادهتر میکند و روشهای معمول بهکارگیری توابع را شفّافتر میکند.
برای مثال اگر متوجّه شوید که بیشتر فراخوانیهای تابع describe_pets() برای توصیف سگها به کار میروند، میتوانید مقدار پیشفرض animal_type را 'dog' قرار دهید. حال هرکس هنگام فراخوانی describe_pet() برای یک سگ میتواند آن بخش از اطّلاعات ورودی را حذف کند.
defdescribe_pet(pet_name, animal_type='dog'):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet(pet_name='willie')
تعریفِ describe_pet() را تغییر دادیم تا مقدار پیشفرضِ 'dog' را برای animal_type شامل شود. اکنون هرگاه تابع بدون تعیینِ animal_type فراخوانی شود، پایتون میداند که مقدار 'dog' را به جای این پارامتر به کار برد:
توجّه داشته باشید که ترتیب پارامترها در تعریفِ تابع باید تغییر یابد. از آنجایی که وجودِ مقدارِ پیشفرض، لزوم تعریف نوع حیوان را به عنوان پارامتر از بین برده، تنها نشانوندی که در فراخوانیِ تابع باقی میماند نام حیوان خانگی است. پایتون این نشانوند را همچنان مکاندار تلقّی میکند پس اگر این تابع فقط با نام حیوان خانگی فراخوانده شود، آن نشانوند با اولّین پارامتری که در تعریفِ تابع آمده، جفت میشود. به این دلیل است که پارامتر اوّل باید pet_name باشد.
اکنون سادهترین روش بهکارگیری این تابع این است که هنگام فراخوانی آن تنها نام سگ را ارائه کنیم:
خروجیِ این فراخوانیِ تابع مشابه مثال قبل است. تنها نشانوندی که ارائه شده 'willie' است، بنابراین این مقدار با اوّلین پارامتر در تعریفِ تابع، pet_name، جفت میشود. از آنجاییکه هیچ نشانوندی برای animal_type آورده نشده، پایتون از مقدارِ پیشفرضِ 'dog' استفاده میکند.
برای توصیف حیوانی به جز یک سگ، میتوانید تابع را به این شکل فراخوانی کنید:
پایتون مقدارِ پیشفرضِ پارامتر را نادیده میگیرد چون این بار صریحاً نشانوندی برای animal_type تعیین شده است.
نکته
هنگامی که از مقدارهای پیشفرض استفاده میکنید، پارامترهایی که مقدار پیشفرض دارند باید بعد از پارامترهایی بیایند که برای آنها مقدار پیشفرض تعریف نشده است. این کار به پایتون امکان میدهد که نشانوندهای مکاندار را به درستی تفسیر کند.
فراخوانیهای تابع همسان
از آنجایی که نشانوندهای مکاندار، نشانوندهای کلیدواژهای و مقدارهای پیشفرض میتوانند همگی در کنار هم به کار روند، اغلب برای فراخوانی یک تابع چندین راه پیش رو دارید. تعریف زیر را برای describe_pet() با یک مقدارِ پیشفرض در نظر بگیرید:
با چنین تعریفی همواره باید نشانوندی به جای pet_name ارائه کرد و این مقدار میتواند به صورت مکاندار یا کلیدواژهای باشد. اگر حیوانی که توصیف میشود، سگ نباشد، نشانوندی هم باید برای animal_type ارائه شود که آن نیز میتواند به شکل کلیدواژهای یا مکاندار باشد.
تمام فراخوانیهای زیر برای این تابع قابل استفادهاند:
# A dog named Willie.describe_pet('willie')
describe_pet(pet_name='willie')
# A hamster named Harry.describe_pet('harry', 'hamster')
describe_pet(pet_name='harry', animal_type='hamster')
describe_pet(animal_type='hamster', pet_name='harry')
تمام این فراخوانیهای خروجیای مشابه با قبل خواهند داشت.
نکته
در واقع اهمیتی ندارد که از کدام شیوه برای فراخوانی استفاده میکنید. مادامی که فراخوانی تابع به نتیجهی مطلوب منجر میشود، کافی است از شیوهای استفاده کنید درک آن برای شما سادهتر است.
پرهیز از خطاهای نشانوندی
هنگامی که بهکارگیری تابعها را آغاز میکنید، برخوردن به خطاهایی در رابطه با نشانوندهای نابهجا یا به درستی جفتنشده، نباید موجب شگفتی شما شود. این عدم تطابق زمانی رخ میدهد که شما نشانوندهایی کمتر یا بیشتر از آنچه تابع نیاز دارد، ارائه میکنید. برای مثال ببینید اگر describe_pets() را بدون هیچ نشانوندی فرابخوانیم چه اتّفاقی میافتد:
defdescribe_pet(animal_type, pet_name):
"""Display information about a pet.""" print(f"\nI have a {animal_type}.")
print(f"My {animal_type}'s name is {pet_name.title()}.")
describe_pet()
پایتون تشخیص میدهد که برخی اطّلاعات در فراخوانی تابع جا افتاده است و tracebackای در این مورد به ما اطّلاعات میدهد:
Traceback (most recent call last):
File "pets.py", line 6, in <module>
describe_pet()
TypeError: describe_pet() missing 2 required positional arguments:
'animal_type' and 'pet_name'
در ۱ traceback محل وقوع مشکل را به ما میگوید و به ما امکان میدهد که برگردیم و ببینیم که در فراخوانی تابع چه مشکلی وجود دارد. در ۲ تابعی که فراخوانی آن به مشکل خورده بیان شده است. در ۳ traceback میگوید که دو نشانوند در فراخوانی تابع جا افتاده و نام آنها گزارش شده است. اگر این تابع در تابعی جدا نوشته شده بود، میتوانستیم مشکل فراخوانی را بدون باز کردن آن فایل و خواندن کد آن تابع برطرف کنیم.
این موضوع که پایتون کد تابع را میخواند و نام نشانوندهای جاافتاده را به ما ارائه میکند، سودمند است. این میتواند انگیزهای باشد که برای تابعها و متغیّرهای خود نامهای گویا و پرمعنی انتخاب کنید. اگر چنین کنید پیامهای خطای پایتون میتواند برای شما و هرکس که از کد شما استفاده میکند، مفیدتر باشد.
اگر تعداد نشانوندها بیش از حد نیاز باشد، به tracebackای مشابه برمیخورید که به شما کمک میکند فراخوانی تابع را با تعریفِ آن جور کنید.
خودآزمایی
۸-۳. تیشرت: تابعی به نام make_shirt() بنویسید که اندازه تیشرت و متن پیامی را که قرار است روی آن چاپ شود دریافت کند. این تابع باید پیامی چاپ کند که شامل پیامی خلاصه در مورد اندازه و جملهی چاپشده روی تیشرت باشد.
تابع را یکبار به کمک نشانوندهای مکاندار فراخوانی کنید تا اطلاعات تیشرتی را تولید کند. بار دیگر تابع را این بار به کمک نشانوندهای کلیدواژهای فراخوانی کنید.
۸-۴. تیشرتهای سایز بزرگ: تابع make_shirt() را طوری تغییر دهید که سایز تیشرتها به صورت پیشفرض بزرگ و پیام چاپشده I love Python باشد. یک تیشرت بزرگ و متوسط با پیام پیشفرض و یک تیشرت با پیامی متفاوت (و در هر سایزی که خواستید) تولید کنید.
۸-۵. شهرها: تابعی به نام describe_city() بنویسید که نام یک شهر و کشور آن را دریافت میکند. این تابع باید جملهای ساده چاپ کند، مانند Reykjavik is in Iceland. به پارامتر مربوط به کشور یک مقدارِ پیشفرض بدهید. تابع را برای سه شهر مختلف فراخوانی کنید که دستِ کم در یکی از آنها از کشور پیشفرض استفاده نشود.
8.4 - مقدارهای برگشتی
دلیلی ندارد که تابع همیشه خروجی را به طور مستقیم نمایش دهد. در عوض میتواند دادههایی را پردازش کند و مقدار یا مجموعهای از مقدارها را برگرداند. به مقداری که تابع برمیگرداند مقدار برگشتی گویند. عبارت return مقداری را از داخل تابع میگیرد و به همان خطّی که تابع در آن فراخوانی شده بود برمیگرداند. با مقدارهای برگشتی میتوان بخش زیادی از کارهای برنامه را به تابعها منتقل کرد که این، باعث سادهتر شدن بدنهی برنامهی شما میشود.
بازگرداندن یک مقدار ساده
تابعی را میبینیم که نام و نام خانوادگی را میگیرد و نام کامل را به شکلی مرتّب برمیگرداند:
defget_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted.""" full_name =f"{first_name}{last_name}"return full_name.title()
musician = get_formatted_name('jimi', 'hendrix')
print(musician)
تعریف تابع در ۱ دو پارامتر شامل نام و نام خانوادگی دریافت میکند. تابع این دو نام را ترکیب میکند، بین آنها یک فاصلهی خالی قرار میدهد و حاصل را به full_name نسبت میدهد (۲). حرفهای اوّل مقدار full_name به حروف بزرگ تبدیل میشود و به خطّی که تابع را فراخوانده برگردانده میشود. (۳)
وقتی تابعِ تعریفشده مقداری را برمیگرداند باید متغیّری ارائه کنید که مقدار برگشتی به آن منسوب شود. در این مورد مقدار برگشتی به متغیّر musician نسبت داده میشود (۴). خروجی نامی مرتّب را نشان میدهد که از اجزای نام یک فرد تشکیل شده است:
امّا برنامهای بزرگ را در نظر بگیرید که باید تعداد زیادی نام و نام خانوادگی را جداگانه ذخیره کند. در چنین برنامهای تابعهایی مثل get_formatted_name() بسیار سودمند خواهد بود. نامها و نامهای خانوادگی را جداگانه ذخیره میکنید و سپس هرگاه نیاز داشتید آن را به صورت نام کامل نمایش میدهید.
اختیاری کردن یک نشانوند
اختیاری کردن تعیینِ یک نشانوند، گاهی کاری معقول است تا کاربران بتوانند اطّلاعات اضافی را تنها زمانی وارد کنند که به آنها نیاز است. میتوانید از مقدارهای پیشفرض برای اختیاری کردن یک نشانوند استفاده کنید.
برای مثال فرض کنید میخواهیم get_formatted_name() را طوری گسترش دهیم که نامهای میانی را هم دربربگیرد. اولّین تلاش برای چنین کاری ممکن است اینگونه باشد:
defget_formatted_name(first_name, middle_name, last_name):
"""Return a full name, neatly formatted.""" full_name =f"{first_name}{middle_name}{last_name}"return full_name.title()
musician = get_formatted_name('john', 'lee', 'hooker')
print(musician)
این تابع زمانی کار میکند که نام، نام میانی و نام خانوادگی به آن داده شود. تابع هر سه بخش نام را گرفته و با آنها رشتهای میسازد. سپس جاهایی که نیاز باشد فاصله اضافه کرده و نام کامل را به حالت عنوان در میآورد:
امّا نام میانی همواره مورد نیاز نیست و این تابع جوری نوشته شده که اگر فقط نام و نام خانوادگی را به آن بدهید کار نمیکند. برای اختیاری کردن نام میانی میتوانیم به نشانوند middle_name یک مقدار پیشفرض خالی بدهیم و از آن جز در مواردی که کاربر مقدار ارائه کرده، صرف نظر کنیم. برای این که کاری کنیم که get_formatted_name() بدون نام میانی کار کند، مقدار پیشفرض middle_name را یک رشتهی خالی میگذاریم و آن را به انتهای فهرست پارامترها منتقل میکنیم:
در این مثال نام از سه بخش تشکیل شده است. چون نام و نام خانوادگی همواره باید ارائه شوند، این دو پارامتر را در تعریفِ تابع در ابتدا آوردهایم. نام میانی اختیاری است بنابراین پس از این دو پارامتر میآید و مقدار پیشفرض آن رشتهای خالی است. (۱)
در بدنهی تابع بررسی میکنیم که آیا نام میانی وارد شده یا نه. پایتون رشتههای ناتُهی را True تلقّی میکند پس اگر middle_name درست (True) ارزیابی شود به این معنی است که هنگام فراخوانی تابع برای آن مقداری تعیین شده است. (۲). اگر نام میانی ارائه شده باشد، نام، نام میانی و نام خانوادگی با هم ترکیب میشوند تا نام کامل ساخته شود. نام کامل به حالت عنوان درآمده و به خطّ فراخوانی تابع برگردانده میشود؛ جایی که مقدار آن به متغیّر musician نسبت داده شده و چاپ میشود. اگر نام میانی وارد نشده باشد، آزمون شرطی if رد میشود و بخش else اجرا میشود (۳). نام کامل تنها با نام و نام خانوادگی ساخته میشود و نام شکلدادهشده به خط فراخوانی برگردانده شده و چاپ میشود.
فراخوانی تابع با نام و نام خانوادگی سرراست است. اگر بخواهیم نام میانی را هم وارد کنیم، باید مطمئن شویم که آن را به عنوان آخرین نشانوند بنویسیم تا پایتون بتواند نشانوندهای مکاندار را به درستی تطبیق دهد. (۴)
نسخهی تغییریافتهی تابع برای افرادی که تنها نام و نام خانوادگی دارند کار میکند، و به همان ترتیب برای افرادی که نام میانی هم دارند، عمل میکند:
مقدارهای اختیاری این امکان را به تابعها میدهد که کاربردهای بسیار متنوّعی را پوشش دهند در عین حالی که فراخوانی آنها تا جای ممکن ساده باشد.
بازگرداندن یک دیکشنری
یک تابع میتواند هر نوع مقداری را که نیاز داشته باشید برگرداند، از جمله ساختمانهای دادهی پیچیدهتر مانند لیستها و دیکشنریها. برای مثال تابع زیر اجزای نام را میگیرد و یک دیکشنری را که نمایندهی یک فرد است برمیگرداند:
defbuild_person(first_name, last_name):
"""Return a dictionary of information about a person.""" person = {'first': first_name, 'last': last_name}
return person
musician = build_person('jimi', 'hendrix')
print(musician)
تابع build_person() نام و نام خانوادگی را میگیرد و این مقدارها را در ۱ در یک دیکشنری قرار میدهد. مقدار first_name در کلید 'first' و مقدار last_name در کلید 'last' ذخیره میشود. کل دیکشنری که نمایندهی یک فرد است در ۲ برگردانده میشود. مقدار برگشتی در ۳ چاپ میشود که نشاندهندهی دو دانستهی متنی است که این بار در یک دیکشنری ذخیره شده است:
این تابع دو دادهی متنی ساده را دریافت کرده و آن در یک ساختمان دادهی بامعناتر قرار میدهد که به شما امکان میدهد با دادهها کارهایی فراتر از چاپ کردن بکنید. رشتههای 'jimi' و 'hendrix' اکنون به عنوان نام و نام خانوادگی برچسب خوردهاند. میتوانید این تابع را به سادگی گسترش دهید تا مقدارهای اختیاری مختلفی بگیرد؛ همچون نام میانی، سن، شغل و یا هر اطّلاعاتی دیگری که بخواهید در مورد یک فرد ذخیره کنید. برای مثال تغییر زیر موجب میشود که بتوانید سن فرد را هم ذخیره کنید:
defbuild_person(first_name, last_name, age=None):
"""Return a dictionary of information about a person.""" person = {'first': first_name, 'last': last_name}
if age:
person['age'] = age
return person
musician = build_person('jimi', 'hendrix', age=27)
print(musician)
پارامتر اختیاری سن را به تعریفِ تابع افزودهایم و77
به آن مقدار ویژهی None را نسبت دادهایم. این مقدار زمانی استفاده میشود که متغیّر هیچ مقدار خاصّی ندارد. به None میتوانید به عنوان یک نگهدارنده یا جایبان نگاه کنید. در آزمونهای شرطی None نادرست (False) ارزیابی میشود. اگر هنگام فراخوانیِ تابع سن وارد شود، مقدار آن در دیکشنری ذخیره میگردد. این تابع همواره نام فرد را ذخیره میکند امّا میتوان آن را جوری تغییر داد که هر اطّلاعات دیگری را در مورد یک فرد در بر بگیرد.
بهکارگیری تابع با یک حلقهی while
میتوانید از تابعها در تمام ساختارهای پایتون که تاکنون آموختهاید استفاده کنید. برای مثال بیایید از تابع get_formatted_name() به همراه یک حلقهی while استفاده کنیم تا به کاربران کمی رسمیتر سلام کنیم. این اوّلین تلاش برای سلام کردن به افراد با نام و نام خانوادگی آنهاست:
defget_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted.""" full_name =f"{first_name}{last_name}"return full_name.title()
# This is an infinite loop!whileTrue:
print("\nPlease tell me your name:")
f_name = input("First name: ")
l_name = input("Last name: ")
formatted_name = get_formatted_name(f_name, l_name)
print(f"\nHello, {formatted_name}!")
در این مثال از نسخهی سادهتر get_formatted_name() استفاده کردهایم که نامهای میانی را شامل نمیشود. حلقهی while از کاربران نام و نام خانوادگی آنها را میپرسد. گرفتن نام و نام خانوادگی جداگانه انجام میپذیرد. (۱)
امّا این حلقهی while یک مشکل دارد: شرط خروج برای آن تعیین نکردهایم. هنگامی که چند ورودی داریم شرط خروجی را باید در کجا قرار دهیم؟ ما میخواهیم که کاربر قادر باشد به سادهترین راه ممکن از برنامه خارج شود پس هر کدام از اعلانها باید راهی برای خروج داشته باشند. عبارت break روش سرراستی برای خروج از حلقه در هر یک از اعلانهاست:
defget_formatted_name(first_name, last_name):
"""Return a full name, neatly formatted.""" full_name =f"{first_name}{last_name}"return full_name.title()
whileTrue:
print("\nPlease tell me your name:")
print("(enter 'q' at any time to quit)")
f_name = input("First name: ")
if f_name =='q':
break l_name = input("Last name: ")
if l_name =='q':
break formatted_name = get_formatted_name(f_name, l_name)
print(f"\nHello, {formatted_name}!")
پیامی اضافه کردهایم که به کاربر اطّلاع دهد چگونه میتواند خارج شود و سپس اگر کاربر مقدار خروج را در هرکدام از اعلانها وارد کرد، حلقه را میشکنیم. اکنون برنامه به سلام کردن به افراد ادامه میدهد تا جایی که کسی 'q' را به جای هر کدام از نامها وارد کند:
Please tell me your name:
(enter 'q' at any time to quit)
First name: **eric**
Last name: **matthes**
Hello, Eric Matthes!
Please tell me your name:
(enter 'q' at any time to quit)
First name: **q**
خودآزمایی
۸-۶. نام شهرها: تابعی به نام city_country() بنویسید که نام و کشور یک شهر را بگیرد. تابع باید رشتهای به شکل زیر برگرداند:
تابع خود را با دستِ کم سه جفت شهر-کشور فراخوانی کنید و مقدارهای خروجی را چاپ کنید.
۸-۷. آلبوم: تابعی به نام make_album() بنویسید که یک دیکشنری برای توصیف یک آلبوم موسیقی میسازد. این تابع باید نام هنرمند و عنوان آلبوم را بگیرد و یک دیکشنری شامل این اطّلاعات را برگرداند. از تابع برای ساختن سه دیکشنری که هر کدام بیانگر یک آلبوم موسیقی است، استفاده کنید. مقدار برگشتی هر کدام را چاپ کنید تا نشان دهید دیکشنریها اطّلاعات آلبوم را به درستی ذخیره میکنند.
از None برای اضافه کردن یک پارامتر اختیاری به make_album() استفاده کنید تا امکان ذخیره کردن تعداد آهنگهای یک آلبوم را هم فراهم آورد. اگر در خطّ فراخوانی مقداری هم برای تعداد آهنگها وارد شود، آن مقدار را به دیکشنری آلبوم اضافه کنید. حداقل یک فراخوانیِ تابع اضافه کنید که شامل تعداد آهنگهای یک آلبوم باشد.
۸-۸. آلبومهای کاربران: با برنامهی تمرین ۸-۷ شروع کنید. یک حلقهی while بنویسید که به کاربران امکان دهد نام و نام هنرمند یک آلبوم را وارد کنند. به محض دریافت اطّلاعات make_album() را با اطّلاعات کاربران فراخوانی کنید و دیکشنریای را که ساخته شده نمایش دهید. مطمئن شوید که یک مقدار خروج در حلقهی while تعیین کردهاید.
8.5 - انتقال لیست
انتقال لیستها به یک تابع غالباً کاری سودمند است، چه لیستی از نامها باشد، چه اعداد یا چیزهای پیچیدهتر مثل دیکشنریها. زمانی که لیستی را به تابعی میدهید، تابع به محتویّات آن دسترسی مستقیم دارد. اکنون از تابعها برای کار کردن کارآمدتر با لیستها استفاده میکنیم.
فرض کنید که لیستی از کاربران داریم و میخواهیم به هر کدامشان سلام کنیم. مثال زیر لیستی از نامها را به تابعی به نام greet_users() میفرستد که به هر کدام از افراد داخل لیست به طور جداگانه سلام میکند.
defgreet_users(names):
"""Print a simple greeting to each user in the list."""for name in names:
msg =f"Hello, {name.title()}!" print(msg)
usernames = ['hannah', 'ty', 'margot']
greet_users(usernames)
تابع greet_users() را طوری تعریف کردهایم که لیستی از نامها را بپذیرد و آن را به پارامتر names نسبت دهد. تابع داخل لیست دریافتی حلقه میزند و به هر کدام از کاربرها سلام میکند. در ۱ لیستی از کاربران تعریف کردهایم و آن را (usernames) هنگام فراخوانی به تابع greet_users() دادهایم:
این همان خروجیای است که میخواستیم. هر کاربر پیامی شخصی دریافت میکند و شما میتوانید تابع را هر زمان برای سلام کردن به گروه مشخّصی از کاربران به کار بگیرید.
تغییر دادن یک لیست در داخل تابع
زمانی که لیستی را به یک تابع ارسال میکنید، آن تابع میتواند آن را تغییر دهد. تغییراتی که در داخل بدنهی تابع روی لیست اعمال میکنید دائمیاند که این باعث میشود بتوانید با تعداد بسیار زیاد داده هم به خوبی کار کنید.
شرکتی را در نظر بگیرید که بر اساس طرّاحیهایی که کاربران ثبت میکنند، مدلهای چاپ سهبعدی میسازد. طرحهایی که باید چاپ شوند در یک لیست ذخیره شده و پس از چاپ شدن به لیستی جداگانه منتقل میشوند. کد زیر این کار را بدون بهکارگیری تابعها انجام میدهد:
# Start with some designs that need to be printed.unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
# Simulate printing each design, until none are left.# Move each design to completed_models after printing.while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
# Display all completed models.print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
این برنامه با دو لیست شروع میشود: یکی لیستِ طرحهایی که باید چاپ شوند و دیگری لیستی خالی که طرحها پس از چاپ شدن به آن منتقل میشوند. تا زمانی که طرحی در unprinted_designs باقی مانده باشد، حلقهی while با خارج کردن طرح از انتهای لیست، ذخیره کردن آن در current_design و چاپ کردن پیامی مبنی بر در حالِ چاپ بودنِ طرح فعلی، عملیّات چاپ را شبیهسازی میکند. حلقه پس از آن طرح را به لیستِ مدلهای کاملشده اضافه میکند. زمانی که حلقه به پایان رسید، فهرستی از طرحهایی که چاپ شدهاند، نمایش داده میشود:
Printing model: dodecahedron
Printing model: robot pendant
Printing model: phone case
The following models have been printed:
dodecahedron
robot pendant
phone case
میتوانیم این کد را با نوشتن دو تابع که هر یک کار خاصّی انجام میدهند، بازآرایی کنیم. بیشترِ کد تغییر نخواهد کرد؛ تنها آن را با دقّت بیشتری ساختاردهی میکنیم. تابع اوّل چاپ کردن طرحها را مدیریّت میکند و دومی کارهای چاپی را که انجام شده خلاصه میکند:
defprint_models(unprinted_designs, completed_models):
"""Simulate printing each design, until none are left.
Move each design to completed_models after printing."""while unprinted_designs:
current_design = unprinted_designs.pop()
print(f"Printing model: {current_design}")
completed_models.append(current_design)
defshow_completed_models(completed_models):
"""Show all the models that were printed.""" print("\nThe following models have been printed:")
for completed_model in completed_models:
print(completed_model)
unprinted_designs = ['phone case', 'robot pendant', 'dodecahedron']
completed_models = []
print_models(unprinted_designs, completed_models)
show_completed_models(completed_models)
در ۱ تابع print_models() را با دو پارامتر تعریف میکنیم: لیستی از طرحهایی که باید چاپ شوند و لیستی از مدلهای تکمیلشده. تابع با گرفتنِ این دو لیست، فرآیند چاپ را با خالی کردنِ لیست طرحهای چاپنشده و پر کردنِ لیستِ مدلهای تکمیلشده شبیهسازی میکند. در ۲ تابع show_completed_models() را با یک پارامتر تعریف میکنیم: لیستِ مدلهای تکمیلشده. تابع show_completed_models() با داشتنِ این لیست، نام هر کدام از مدلهای چاپشده را نمایش میدهد.
خروجیِ این برنامه مشابه نسخهی بدون تابعِ آن است امّا کد آن بسیار سازمانیافتهتر است. کدی که بیشترِ کارها را انجام میدهد به دو تابع مجزّا منتقل شده است که باعث میشود درک بخش اصلی برنامه سادهتر باشد. به بدنهی برنامه نگاه کنید تا ببینید که درک آنچه که انجام میدهد چهقدر سادهتر است:
لیستی از طرحهای چاپنشده و یک لیست خالی که مدلهای تکمیلشده را نگه میدارد تعریف میکنیم. پس از آن، چون قبلاً دو تابع خود را تعریف کردهایم، تنها آنها را با پارامترهای درست فراخوانی کنیم. تابع print_models() را صدا کرده و دو لیست مورد نیاز را به آن میدهیم؛ چنانکه انتظار داشتیم این تابع چاپ کردن طرحها را شبیهسازی میکند. سپس show_completed_models() را فراخوانی کرده و لیست مدلهای تکمیلشده را به آن میدهیم تا بتواند چاپ شدنِ آنها را گزارش کند. این نامهای تابع گویا به دیگران این امکان را میدهد که کد را حتّی بدون توضیحات، خوانده و درک کنند.
نگهداری و توسعهی این برنامه از نسخهی بدونِ تابعِ آن سادهتر است. اگر بخواهیم بعداً طرحهای دیگری را چاپ کنیم به سادگی تابع print_models() را دوباره فرامیخوانیم. اگر متوجّه شویم که کد مربوط به چاپ کردن باید تغییر کند، کد را یک بار تغییر میدهیم و هر جا که تابع استفاده شده باشد، تمام تغییراتِ اعمالی اعمال میشوند. این روش از تغییر دادن کد در جای جایِ برنامه کارآمدتر است.
این مثال همچنین این ایده را نشان میدهد که هر تابع بهتر است تنها یک وظیفهی مشخّص داشته باشد. تابع اوّل طرحها را چاپ میکند و دومی مدلهای تکمیلشده را نشان میدهد. این کار از این که از یک تابع برای انجام هر دو کار استفاده کنیم، مفیدتر است. اگر در حال نوشتن تابعی هستید و متوجّه میشوید که تابع کارهای بسیار متفاوتی را انجام میدهد، سعی کنید آن را به دو تابع تقسیم کنید. فراموش نکنید که همواره میتوانید تابعی را در تابع دیگری فراخوانی کنید که این کار هنگام تقسیم یک کار پیچیده به دنبالهای از مراحل مفید است.
منعِ یک تابع از تغییر دادن یک لیست
گاهی شما میخواهید مانع تغییر یافتنِ یک لیست در تابع شوید. برای نمونه فرض کنید میخواهید از لیست طرحهای چاپنشده شروع کنید و تابعی بنویسید که مانند مثال قبل آنها را به لیست مدلهای تکمیلشده منتقل کند. شاید تصمیم بگیرید علاوه بر چاپ کردنِ تمام طرحها، لیست اصلی طرحهای چاپنشده را برای بایگانی نگه دارید. امّا از آنجایی که نام تمامِ طرحها را از unprinted_designs خارج کردهاید، این لیست اکنون خالی است و لیستِ خالی تنها نسخهای است که در اختیار دارید؛ لیست اصلی از بین رفته است. در چنین حالتی میتوانید مشکل را با ارسال یک کپی از لیست (نه اصلِ آن) به تابع حل کنید. هر تغییری که تابع در لیست ایجاد کند تنها کپی آن را تحت تاثیر قرار میدهد و لیست اصلی دستنخورده باقی میماند.
میتوانید کپیای از لیست را به این صورت به تابع ارسال کنید:
نماد برش [:] یک کپی از لیست را به تابع میفرستد. اگر قصد نداشتیم که لیست طرحهای چاپنشده را در printing_models.py دست بزنیم، باید print_models() را مانند زیر فراخوانی میکردیم:
تابع print_models() میتواند به کار خود برسد زیرا همچنان نام تمام طرحهای چاپنشده را دریافت میکند. امّا این بار این از کپیِ لیست اصلی طرحهای چاپنشده استفاده میکند و نه لیستِ unprinted_designs واقعی. لیست completed_models مثل قبل با نام مدلهای چاپشده پر میشود امّا لیستِ اصلیِ طرحهایِ چاپنشده در این تابع تغییری نمیکند.
اگرچه با ارسال یک کپی از لیست به تابع میتوانید محتویّات آن را حفظ کنید، امّا تا زمانی که دلیل مشخّصی برای ارسال کپی ندارید، باید اصلِ لیست را بفرستید. کار کردن با لیستی که قبلاً ایجاد شده برای یک تابع کارآمدتر است زیرا از صرف زمان و حافظه برای ساختن یک لیست جداگانه پرهیز میکند، به ویژه هنگامی که با لیستهای بزرگ سروکار دارید.
خودآزمایی
۸-۹. پیامکها: لیستی شامل تعدادی پیامک بسازید. لیست را به تابع show_message() بفرستید تا هر پیامک را چاپ کند.
۸-۱۰. ارسال پیامکها: از یک کپی از برنامهی تمرین ۸-۹ شروع کنید. تابعی به نام send_messages() بنویسید که هر پیامک را چاپ کرده و آن را پس از چاپ به لیستی جدید به نام sent_messages منتقل میکند. پس از فراخوانی تابع هر دو لیست را چاپ کنید تا مطمئن شوید که پیامکها به درستی منتقل شدهاند.
۸-۱۱. پیامکهای بایگانیشده: کار خود را از تمرین ۸-۱۰ شروع کنید. تابع send_messages() را با یک کپی از لیست پیامکها فراخوانی کنید. بعد از صدا کردنِ تابع هر دو لیست را چاپ کنید و نشان دهید که لیست اصلی پیامکهای خود را حفظ کرده است.
8.6 - انتقال تعداد دلخواهی نشانوند
گاهی از تعداد نشانوندهایی که یک تابع نیاز دارد، از پیش اطّلاع ندارید. خوشبختانه پایتون این امکان را میدهد که تابع تعداد دلخواهی نشانوند را هنگام فراخوانی دریافت کند.
برای مثال تابعی را در نظر بگیرید که پیتزا درست میکند. این تابع به تعداد مواد روی پیتزا نیاز دارد ولی شما از پیش نمیدانید که فرد چه تعدادی از این مواد را درخواست میکند. تابع مثال زیر یک پارامتر دارد، *toppings، امّا این پارامترها تمام نشانوندهایی را که در خط فراخوانی ارائه میشود، جمعآوری میکند:
defmake_pizza(*toppings):
"""Print the list of toppings that have been requested.""" print(toppings)
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
علامت ستاره در نام پارامتر *toppings به پایتون میگوید که یک چندتایی به نام toppings درست کن و تمام مقدارهایی را که دریافت میشود درونِ آن قرار بده. خروجیِ تولیدشده با فراخوانی print() در بدنهی تابع نشان میدهد که پایتون میتواند فراخوانیِ تابع را با یک یا سه مقدار مدیریّت کند و با فراخوانیهای مختلف یک جور برخورد میکند. توجّه کنید که پایتون نشانوندها را در یک چندتایی قرار میدهد، حتّی اگر تابع تنها یک مقدار ورودی داشته باشد:
defmake_pizza(*toppings):
"""Summarize the pizza we are about to make.""" print("\nMaking a pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza('pepperoni')
make_pizza('mushrooms', 'green peppers', 'extra cheese')
تابع چه یک مقدارِ ورودی بگیرد چه سه مقدار، پاسخِ متناسبی میدهد:
Making a pizza with the following toppings:
- pepperoni
Making a pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
این شکلِ دستوری صرف نظر از تعداد نشانوندهایی که تابع دریافت کرده، کار میکند.
ترکیب کردن نشانوندهای مکاندار و اختیاری
اگر بخواهید که تابعی انواعِ مختلفی نشانوند را بپذیرد، پارامتری که برای تعداد نشانوندهای اختیاری تعریف میشود، باید هنگام تعریفِ تابع پس از دیگر پارامترها قرار بگیرد. پایتون ابتدا نشانوندهای مکاندار و کلیدواژهای را جفت و سپس در پارامتر آخر تمام نشانوندهای باقیمانده را جمعآوری میکند.
برای مثال اگر تابع به پارامتری برای اندازهی پیتزا هم نیاز داشته باشد، باید آن را پیش از پارامتر *toppings بیاوریم:
defmake_pizza(size, *toppings):
"""Summarize the pizza we are about to make.""" print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
پایتون در تعریفِ تابع، اوّلین مقداری را که دریافت میکند به پارامتر size نسبت میدهد. تمام مقدارهای دیگر که پس از آن میآیند در چندتایی toppings ذخیره میشوند. فراخوانیهای این تابع ابتدا شامل نشانوندی برای اندازهی پیتزا هستند و پس از هر تعداد مواد روی پیتزا که مورد نیاز باشد، به تابع ارسال میشود.
اکنون هر پیتزا یک اندازه و تعدادی مواد دلخواه دارد و هر بخش اطّلاعات در جای مناسب خود چاپ میشود که نمایانگر اندازه در ابتدا و سپس مواد روی پیتزاست:
Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
نکته
شما اغلب پارامتری عمومی به نام *args میبینید که تمام نشانوندهای مکاندار مانند این را جمعآوری میکند.
بهکارگیری نشانوندهای کلیدواژهای اختیاری
گاهی میخواهید تعدادی نشانوند اختیاری را بپذیرید امّا از پیش نمیدانید که چه نوع اطّلاعاتی قرار است به تابع ارسال شود. در این مورد میتوانید تابعهایی بنویسید که هر تعداد جفت کلید-مقدار را که هنگام فراخوانی ارائه شود، بپذیرد. یک نمونه هنگام ساختن نمایهی کاربری است: شما میدانید که اطّلاعاتی در مورد یک کاربر دریافت میکنید امّا مطمئن نیستید آنها از چه نوعی هستند. تابع build_profile() در مثال زیر همیشه نام و نام خانوادگی را دریافت میکند، امّا تعداد دلخواهی نشانوند کلیدواژهای را هم میپذیرد:
defbuild_profile(first, last, **user_info):
"""Build a dictionary containing everything we know about a user.""" user_info['first_name'] = first
user_info['last_name'] = last
return user_info
user_profile = build_profile('albert', 'einstein',
location='princeton',
field='physics')
print(user_profile)
تعریف build_profile() انتظار دارد که نام و نام خانوادگی وارد شود و سپس اجازه میدهد که کاربر هر تعداد جفتِ نام-مقدار که بخواهید وارد کند. دو علامت ستاره پشت **user_info به پایتون میگوید که یک دیکشنری خالی به نام user_info بسازد و تمام جفت نام-مقدارهایی که دریافت میکند در آن قرار دهد. داخل تابع میتوانید همانند هر دیکشنری دیگری، به جفتهای کلید-مقدار user_info دسترسی داشته باشید.
در بدنهی build_profile() همواره نام و نام خانوادگی را به دیکشنری user_info اضافه میکنیم زیرا دو دانسته را از کاربر دریافت میکنیم (۱) امّا باید آنها را در دیکشنری قرار دهیم. سپس دیکشنری user_info را به خط فراخوانی تابع برمیگردانیم.
تابع build_profile() را فراخوانی میکنیم، نام 'albert'، نام خانوادگی 'einstein' و دو جفت کلید-مقدارِ location='princeton' و field='physics' را به آن انتقال میدهیم. profile برگردانده شده را به user_profile نسبت میدهیم و آن را چاپ میکنیم:
دیکشنری برگشتی شامل نام و نام خانوادگی کاربر و در این مورد محل و حوزهی مطالعاتی اوست. این تابع باید بتواند صرف نظر از تعداد جفت کلید-مقدارهای اضافیِ ارائهشده حین فراخوانی، کارکند.
نکته
اغلب به پارامتری به نام **kwargs برمیخورید که برای جمعآوری نشانوندهای کلیدواژهای نامشخّص استفاده میشود.
خودآزمایی
۸-۱۲. ساندویچها: تابعی بنویسید که لیستی از مواد دلخواه داخل ساندویچ را از فرد بگیرد. تابع باید یک پارامتر داشته باشد که تمام مواردی را که در فراخوانی تابع ارائه میشود جمعآوری کند و خلاصهای از ساندویچی که سفارش داده میشود، چاپ کند. تابع را سه بار فرابخوانید و هر بار تعداد متفاوتی نشانوند استفاده کنید.
۸-۱۳. نمایهی کاربری: از نسخهای از user_profile.py در صفحهی *** شروع کنید. با فراخوانی تابع build_profile() برای خود نمایهای بسازید که شامل نام و نام خانوادگی و سه جفت کلید-مقدار باشد که شما را توصیف میکند.
۸-۱۴. خودروها: تابعی بنویسید که اطّلاعات پیرامون یک خودرو را در یک دیکشنری ذخیره میکند. تابع باید همیشه نام سازنده و مدل خودرو را دریافت کند. پس از آن باید تعداد دلخواهی نشانوند کلیدواژهای دریافت کند. تابع را با اطّلاعات الزامی و دو جفت نام-مقدار دیگر فرابخوانید، مانند یک رنگ یا ویژگی سفارشی. تابع شما باید با فراخوانی زیر کار کند:
car = make_car('subaru', 'outback', color='blue', tow_package=True)
دیکشنری برگشتی از تابع را چاپ کنید تا مطمئن شوید که اطّلاعات به درستی ذخیره شدهاند.
8.7 - ذخیره کردن تابعها در ماژولها
یکی از سودمندیهای تابعها این است که بخشهای مختلف کد را از برنامهی اصلی شما جدا میکند. با انتخاب نامهای گویا برای تابعهای خود، دنبال کردن برنامهی اصلی شما بسیار سادهتر خواهد بود. میتوانید یک گام فراتر بروید و تابعهای خود را در فایلی جداگانه به نام ماژول ذخیره و سپس آن ماژول را در برنامهی اصلی خود وارد کنید. عبارت import از پایتون میخواهد که کد موجود در ماژول را در فایل برنامهای که اکنون در حال اجراست، در اختیار قرار دهد.
ذخیرهسازی تابعها در یک فایل جداگانه این امکان را فراهم میآورد که جزئیاتِ برنامهی خود را پنهان و بر روی منطقِ سطحِ بالاتر آن تمرکز کنید. این کار همچنین باعث میشود که بتوانید توابع را در برنامههای مختلف دوباره به کارببرید. زمانی که تابعها را در فایلهای جداگانه ذخیره میکنید، میتوانید آنها را با برنامهنویسان دیگر به اشتراک بگذارید بدون این که برنامهی اصلی در اختیارشان قرار بگیرد. همچنین دانستنِ نحوهی وارد کردن تابعها، به شما اجازه میدهد که از کتابخانههای توابع برنامهنویسانِ دیگر در برنامهی خود استفاده کنید.
چندین راه برای وارد کردن یک ماژول وجود دارد که هر کدام از آنها به طور مختصر معرّفی میشود.
وارد کردن یک ماژول کامل
برای آغاز وارد کردن تابعها، نیاز دارید ابتدا یک ماژول بسازید. یک ماژول فایلی با پسوند .py است و کدی را شامل میشود که میخواهید به برنامهی خود وارد کنید. اکنون ماژولی میسازیم که حاوی تابع make_pizza() است. برای ساختن این ماژول همهچیز را به جز این تابع، از فایل pizza.py حذف میکنیم:
defmake_pizza(size, *toppings):
"""Summarize the pizza we are about to make.""" print(f"\nMaking a {size}-inch pizza with the following toppings:")
for topping in toppings:
print(f"- {topping}")
حال فایلی جداگانه به نام making_pizza.py در همان جایی که pizza.py را ذخیره کردهایم میسازیم. این فایل ماژولی را که اکنون ساختهایم وارد کرده و تابع make_pizza() را دوبار فراخوانی میکند.
زمانی که پایتون این فایل را میخواند، خط import pizza به آن میگوید که فایل pizza.py را باز کن و تمام تابعهای داخل آن را در این برنامه کپی کن. شما در واقع کد کپیشده بین فایلها را نمیبینید زیرا پایتون این کار را درست پیش از اجرای برنامهی شما، پشت صحنه انجام میدهد. همهی چیزی که باید بدانید این است که تمام تابعهایی که در pizza.py تعریف شده، اکنون در making_pizzas.py در دسترس است.
برای فراخوانی تابعی از یک ماژولِ واردشده، نام ماژول را، در اینجا pizza، و پس از آن نام تابع را، در اینجا make_pizza() بنویسید و بین این دو یک نقطه قرار دهید (۱). خروجی این کد و برنامهی اصلی که ماژولی را وارد نمیکرد، یکسان است:
Making a 16-inch pizza with the following toppings:
- pepperoni
Making a 12-inch pizza with the following toppings:
- mushrooms
- green peppers
- extra cheese
اوّلین رویکرد برای وارد کردن، که در آن به سادگی دستور import و نام ماژول را پس از آن مینویسید، تمام تابعهای موجود در ماژول در برنامهی شما در اختیار قرار میدهد. اگر این نوع import برای وارد کردن کامل یک ماژول به نام module_name.py استفاده کنید، هر تابع این ماژول از طریق دستور زیر در دسترس است:
با این دستور دیگر نیازی به استفاده از نقطه برای فراخوانی تابع ندارید. میتوانیم هنگام استفاده از تابع آن را با نام فراخوانی کنیم، زیرا تابع make_pizza() را به طور صریح در عبارت import وارد کردهایم.
استفاده از as برای تعیین نام مستعار 78
برای تابع
اگر نام تابعی که وارد میکنید با نام تابعی که در برنامهی شما وجود دارد تداخل داشته باشد، یا نام آن تابع خیلی طولانی باشد، میتوانید از یک نام مستعار کوتاه و یکتا استفاده کنید — نامی جایگزین که مشابه نام خودمانی برای یک تابع است. این نام مستعار را هنگام وارد کردن تابع به آن اطلاق میکنیم.
در اینجا با وارد کردنِ ماژول به صورت make_pizza as mp، به تابع make_pizza() نام مستعار mp() میدهیم. کلیدواژهی as نام تابع را به نام مستعاری که تعیین کردهاید تغییر میدهد:
from pizza import make_pizza as mp
mp(16, 'pepperoni')
mp(12, 'mushrooms', 'green peppers', 'extra cheese')
عبارت import که اینجا نمایش داده شده نام تابع make_pizza() را در این برنامه به mp() تغییر میدهد. هر زمان که بخواهید میتوانید برای فراخوانی به جای make_pizza() از mp() استفاده کنید و پایتون کد make_pizza() را اجرا خواهد کرد بدون این که با تابع make_pizza() دیگری که احتمالاً در برنامهی خود تعریف کردهاید، هیچ گونه تداخلی به وجود بیاید.
دستور عمومی برای ارائه کردن نام مستعار به این صورت است:
شما همچنین میتوانید برای یک ماژول نام مستعار بگذارید. تعیین یک نام مستعار کوتاه، مانند p به جای pizza، باعث میشود که فراخوانی تابعهای آن ماژول سریعتر انجام گیرد. فراخوانی p.make_pizza() بسیار خلاصهتر از pizza.make_pizza() است:
import pizza as p
p.make_pizza(16, 'pepperoni')
p.make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
در عبارت import به ماژول pizza نام مستعار p دادهایم، امّا تابعهای ماژول نامهای اصلی خود را حفظ میکنند. فراخواندن تابعها با نوشتن p.make_pizza() به جای pizza.make_pizza() نه تنها مختصرتر است بلکه توجّه شما از نام ماژول برمیگرداند و امکان میدهد که روی نامهای گویای تابعهای آن تمرکز کنید. نام این تابعها به وضوح به شما میگویند که این تابع چه میکند و بهکارگیری آنها نسبت به بهکارگیری نام کامل ماژول، برای خوانایی برنامهی شما اهمّیّت بیشتری دارند.
from pizza import*make_pizza(16, 'pepperoni')
make_pizza(12, 'mushrooms', 'green peppers', 'extra cheese')
ستاره در عبارت import به پایتون میگوید که تمام تابعهای ماژول را در فایل برنامه کپی کن. چون تمام تابعها وارد شدهاند، میتوانید آنها را تنها با نام و بدون علامت نقطه استفاده کنید. با این حال بهتر آن است زمانیکه با ماژولهای بزرگتری که خود آنها را ننوشتهاید کار میکنید چنین کاری نکنید: اگر ماژول تابعی داشته باشد که با نامی در پروژهی شما یکسان باشد، ممکن است به نتایج غیرمنتظرهای برسید. پایتون ممکن است تابعها یا متغیّرهای مختلفی را با نام یکسان ببیند و به جای وارد کردن جداگانهی تابعها، آنها را رونویسی کند.
بهترین رویکرد این است که تنها تابع یا توابعی را که میخواهید وارد کنید یا تمام ماژول را وارد کرده و از علامت نقطه استفاده کنید. این کار به کدی واضح منجر میشود که خواندن و درک کردن آن ساده است. این بخش از این جهت در اینجا آمده تا اگر به عبارتهای import مانند این در کد دیگران برخوردید، بتوانید آن را تشخیص دهید:
باید برخی جزئیات را هنگام شکل دادن به تابعها در نظر داشته باشید. نام تابعها باید گویا باشد و در آنها از حروف کوچک و زیرخط استفاده شود. نامهای گویا و بامعنی به شما و دیگران کمک میکند که بفهمید کد چه کاری انجام میدهد. نام ماژولها هم باید از چنین قواعدی پیروی کنند.
هر تابع باید توضیحی داشته باشد که مختصراً بگوید تابع چه میکند. این توضیح باید بلافاصله بعد از تعریفِ تابع و به شکل docstring بیاید. اگر تابع به درستی مستند شده باشد، برنامهنویسان دیگر میتوانند تنها با خواندن توضیحات موجود در docstring از آن استفاده کنند. آنها باید قادر باشند که به کارکرد کد همانگونه که شرح داده شده اعتماد کنند و مادامیکه بدانند نام تابع، نشانوندهایی که نیاز دارد و نوع مقداری که برمیگرداند، چیست باید بتوانند از آن در برنامههای خود استفاده کنند.
اگر برای پارامتری مقدار پیشفرض تعیین میکنید، هیچ فاصلهای نباید در اطراف علامت تساوی قرار بگیرد:
PEP 8 توصیه میکند که تعداد کاراکترهای هر خطّ کد را به ۷۹ عدد محدود کنید تا هرکدام از خطها در پنجرهای با اندازهی معقول قابل نمایش باشد. اگر مجموعهای از پارامترها باعث میشوند که تعریفِ تابع بیش از ۷۹ کاراکتر شود، در تعریفِ تابع و پس از پرانتز باز یک ENTER بزنید. در خطّ بعدی دوبار TAB بزنید تا فهرست نشانوندها را از بدنهی تابع، که یک مرحله تورفتگی دارد، جدا کنید.
اکثر ویرایشگرها خطهای اضافیِ پارامترها را جوری تنظیم میکنند که با تورفتگی خطّ اول تناسب داشته باشد:
deffunction_name(
parameter_0, parameter_1, parameter_2,
parameter_3, parameter_4, parameter_5):
function body...
اگر برنامه یا ماژول شما بیش از یک تابع دارد، میتوانید آنها را با دو خطّ خالی از یکدیگر جدا کنید تا پایان یک تابع و آغاز تابع بعدی آسانتر به چشم بیاید.
تمام عبارتهای import باید در ابتدای یک فایل نوشته شوند. تنها استثنا این است که شما توضیحاتی در اوّل فایل برنامه بیاورید که کلیّت آن را شرح دهد.
خودآزمایی
۸-۱۵. چاپ کردن مدلها: تابعهای مثال printing_models.py را در یک فایل جداگانه به نام printing_functions.py قرار دهید. یک عبارت import بالای printing_models.py بنویسید و فایل را طوری تغییر دهید که از تابعهای واردشده استفاده کند.
۸-۱۶. راههای مختلف وارد کردن: از یکی از برنامههای خود که یک تابع دارد استفاده و آن تابع را در یک فایل مجزّا ذخیره کنید. تابع را در برنامهی اصلی خود وارد کنید و آن را با هر یک از رویکردهای زیر فراخوانی کنید:
import module_name
from module_name import function_name
from module_name import function_name as fn
import module_name as mn
from module_name import*
۸-۱۷. شکلدهی به تابعها: سه برنامه از برنامههایی که در این فصل نوشتهاید انتخاب کنید و مطمئن شوید که در نوشتن آنها شیوهنامهی شکلدهی این بخش رعایت شده باشد.
خلاصه
در این فصل یاد گرفتید که چگونه تابعها را بنویسید و نشانوندها را به آنها بدهید تا تابعها به اطّلاعاتی که برای کار کردن نیاز دارند، دسترسی داشته باشند. آموختید که چگونه از نشانوندهای مکاندار و کلیدواژهای استفاده کنید و چگونه تعداد دلخواهی نشانوند بپذیرید. تابعهایی را که خروجی را نمایش میدادند و آنهایی را که مقدارهایی را برمیگرداندند، دیدید. یاد گرفتید که چگونه تابعها را همراه با لیستها، دیکشنریها، گزارههای if و حلقههای while استفاده کنید. همچنین آموختید که چگونه تابعها را در فایلهای جداگانهای به نام ماژول ذخیره کنید تا فایلهای برنامههای شما سادهتر و قابلفهمتر شوند. در نهایت یاد گرفتید که چگونه تابعهای خود را شکل دهید تا برنامههای شما همچنان ساختار مناسبی داشته باشند و خواندن آنها برای شما و دیگران تا جای ممکن ساده باشد.
یکی از هدفهای شما به عنوان برنامهنویس نوشتنِ کد ساده برای انجام کارِ مورد نظرتان است و تابعها به شما برای رسیدن به چنین هدفی کمک میکنند. آنها این امکان را فراهم میآورند که قطعهکدهایی بنویسید و پس از این که فهمیدید کار میکنند آنها را کناری بگذارید. زمانی که بفهمید تابعی وظیفهی خود را درست انجام میدهد میتوانید اعتماد کنید که همیشه درست کار خواهد کرد و به وظیفهی برنامهنویسی بعدی خود بپردازید.
تابعها شما را قادر میسازند که یک بار کد را بنویسید و هر چند بار که خواستید از آن استفاده کنید. زمانی که به اجرای کد داخل تابع نیاز دارید، تمام کاری که باید انجام دهید نوشتن یک فراخوانیِ یکخطی است و تابع کار خود را انجام خواهد داد. زمانی که بخواهید رفتار تابعی را عوض کنید تنها کافی است که یک بخش کد را تغییر دهید و تغییری که ایجاد کردهاید هر جا که تابع فراخوانی شده باشد اعمال خواهد شد.
بهکارگیری تابعها خواندن برنامههای شما را سادهتر میکند و نامگذاریِ خوبِ تابعها، کاری را که هر بخش برنامه انجام میدهد به طور خلاصه بیان میکند. خواندن دنبالهای از فراخوانی تابعها درک سریعتری از برنامه میدهد تا این که بخواهید چندین بخشِ طولانی کد را بخوانید.
تابعها همچنین آزمودن و اشکالزدایی کد شما را سادهتر میکند. وقتی کل کارِ برنامهی شما در قالب تعدادی تابع انجام میشود که هر کدام وظیفهی خاصّی دارند، آزمودن و نگهداری کدی که نوشتهاید بسیار سادهتر خواهد بود. میتوانید برنامهای جداگانه بنویسید که هر تابع را فراخوانی کند و آن را بیازماید تا مشخّص شود که تابع در تمام وضعیّتهایی که ممکن است به آن بربخورد کار میکند یا نه. زمانی که چنین کنید مطمئن خواهید بود که تابعهای شما در هر بار فراخوانی به خوبی کار میکنند.
در فصل ۹ نوشتنِ کلاسها را میآموزید. کلاسها تابعها و دادهها را در یک بستهبندی منظّم قرار میدهند که میتوانید از آنها به روشهای منعطف و کارآمدی استفاده کنید.
9 - کلاسها
9.1 - ساختن و بهکارگیری یک کلاس
برنامه نویسی شیءگرا یکی از موثّ 79
رترین رویکردها برای نوشتن نرمافزار است. در برنامهنویسی شیءگرا شما کلاسهایی میسازید که بیانگر مسائل دنیای واقعی هستند و سپس شیءها را بر اساس آنها ایجاد میکنید. با ساختنِ یک کلاس رفتاری عمومی را برای دستهای از اشیاء تعیین میکنید.
زمانی که از یک کلاس، چند شیءِ جداگانه میسازیم، هر شیء به طور خودکار به این رفتار عمومی مجهّز میشود؛ پس از آن میتوانید به هر کدام از اشیاء هر ویژگی یا خصلتی که خواستید اضافه کنید. این که وضعیتهای دنیای واقعی با برنامهنویسی شیءگرا چگونه به خوبی مدل میشوند، موجب شگفتی شما خواهد شد.
ساختن شیء از روی یک کلاس را نمونهسازی گویند و شما با نمونههای یک کلاس کار خواهید کرد. در این فصل کلاسها را ایجاد کرده و نمونههایی از روی آنها میسازید. نوع اطّلاعاتی را که ممکن است در نمونهها ذخیره شود، تعیین و همچنین کنشهایی را که میتوان با این نمونهها انجام داد، تعریف میکنید. کلاسهایی را هم برای گسترشِ کارآمدیِ کلاسهای موجود ایجاد خواهید کرد تا کلاسهای مشابه بتوانند به طور موثّری کد را بین خود به اشتراک بگذارند. کلاسهای خود را در ماژولها ذخیره و کلاسهایی را که دیگر برنامهنویسان نوشتهاند در فایلهای برنامهی خود وارد میکنید.
درک برنامهنویسی شیءگرا به شما کمک میکند که جهان را از روزنهی چشم یک برنامهنویس ببینید. این شما را یاری میکند که کد خود را واقعاً درک کنید و نه تنها بفهمید که در خط به خط برنامه چه اتّفاقی در حال رخ دادن است بلکه مفاهیم بزرگتری را در ورای آن نیز درک کنید. درکِ منطق پشتِ کلاسها شما را برای فکر کردنِ منطقی تربیت میکند تا بتوانید برنامههایی بنویسید که تقریباً هر مشکلی را که به آن برمیخورید حل کند.
همچنانکه به تدریج به چالشهای پیچیدهتری برمیخورید، کلاسها کار را برای شما و دیگر برنامهنویسانی که با شما کار میکنند، سادهتر میکنند. زمانی که شما و برنامهنویسان دیگر بر اساس یک نوع منطقِ مشترک برنامه مینویسید، قادر خواهید بود که کار یکدیگر را سادهتر درک کنید. برنامههای شما برای مشارکتکنندگان بسیاری قابل درک خواهد بود که به همگان امکان میدهد که دستاورد بیشتری داشته باشند.
شما میتوانید تقریباً هر چیزی را با کلاسها مدل کنید. کار را با نوشتن کلاسی ساده به نام Dog شروع میکنیم که نمایندهی یک سگ است — نه یک سگ خاص بلکه هر نوع سگی. دربارهی سگهای خانگی چه میدانیم؟ همهی آنها نام و سن دارند. همچنین میدانیم که بیشترِ آنها مینشینند و غلت میزنند. این دو قطعه اطّلاعات (نام و سن) و آن دو رفتار (نشستن و غلت زدن) در کلاس Dog جای میگیرند زیرا بین بیشتر سگها مشترکاند. این کلاس به پایتون میگوید که چگونه شیای را برای نمایش یک سگ بسازد. بعد از این که کلاس ما نوشته شد، از آن برای ساختن نمونههای جداگانه که هر کدام بیانگر یک سگ خاص هستند، استفاده میکنیم.
ساختن کلاس Dog
هر نمونهای که از کلاس Dog ساخته شود یک نام (name) و یک سن (age) ذخیره میکند و به هر سگ دو قابلیّت نشستن (sit()) و غلت زدن (roll_over()) خواهد داد:
classDog:
"""A simple attempt to model a dog."""def__init__(self, name, age):
"""Initialize name and age attributes.""" self.name = name
self.age = age
defsit(self):
"""Simulate a dog sitting in response to a command.""" print(f"{self.name} is now sitting.")
نکتههای فراوانی برای توضیح وجود دارد امّا نگران نباشید. این ساختار را در سراسر این فصل خواهید دید و برای عادت کردن به آن وقتِ زیادی دارید. کلاسی به نام Dog تعریف میکنیم. بنا بر قرارداد، در پایتون نامهایی که با حرف اوّل بزرگ نوشته میشوند، بیانگر نام کلاسها هستند. هیچ پرانتزی در تعریف کلاس وجود ندارد چون آن را از اوّل ایجاد میکنیم. یک docstring برای شرح عملکرد کلاس مینویسیم.
متد __init__()
به تابعی که بخشی از یک کلاس باشد متد میگویند. هر چیزی که دربارهی تابعها آموختهاید در مورد متدها هم صدق میکند، فعلاً تنها تفاوت عملی بین این دو، نحوهی فراخوانی متدهاست. متد __init__() متد ویژهای است که پایتون هر بار که نمونهای بر اساس کلاس Dog میسازد، آن را به طور خودکار اجرا میکند. این متد دو زیرخط در ابتدا و دو زیرخط در انتها دارد، این قراردادی است که مانع تداخل نام پیشفرض متدهای پایتون و نام متدهای شما میشود. مطمئن شوید که حتماً دو زیرخط در دو طرف __init__() گذاشتهاید. اگر فقط یکی بگذارید، متد هنگام استفاده از کلاس به طور خودکار فراخوانی نخواهد شد که ممکن است به بروز خطاهایی بیانجامد که تشخیص آنها مشکل است.
تابع __init__() را طوری تعریف میکنیم که سه پارامتر داشته باشد: self، name و age. پارامتر self در تعریفِ متد الزامی است و باید پیش از دیگر پارامترها ذکر شود. این پارامتر باید در تعریفِ متد آورده شود زیرا زمانیکه پایتون بعداً (هنگامِ ساختن یک نمونه از Dog) این متد را فراخوانی میکند، به طور خودکار نشانوند self را به آن انتقال میدهد. فراخوانی هر کدام از متدهای مربوط به یک نمونه به طور خودکار نشانوند self را انتقال میدهد که در واقع ارجاعی به خودِ نمونه است و به یک نمونهی مجزّا امکان میدهد به مشخّصهها و متدهای یک کلاس دسترسی داشته باشد. در زمانِ ساختنِ نمونهای از Dog، پایتون متد __init__() را از کلاس Dog فراخوانی میکند. یک نام و یک سن را به عنوان نشانوند به Dog() میدهیم؛ self به طور خودکار منتقل میشود و نیازی به ذکر آن نیست. هر بار خواستیم نمونهای از کلاس Dog بسازیم، تنها مقدارهای دو پارامتر آخر، name و age را ارائه میکنیم.
دو متغیّرِ تعریفشده در __init__() هر دو پیشوند self دارند. هر متغیّری که پیشوندِ self داشته باشد در دسترسِ تمام متدهای کلاس خواهد بود و ما نیز از82
طریقِ نمونههایی که از کلاس ساخته میشود به این متغیّرها دسترسی داریم. خط self.name = name مقداری را که به پارامتر name داده شده به متغیّر name نسبت میدهد و سپس این متغیّر، به نمونهای که ساخته میشود الصاق میشود. چنین فرآیندی برای self.age = age هم طی میشود. به متغیّری که به این شکل و از طریق نمونهها در دسترس است مشخّصه میگویند.
در کلاس Dog دو متد دیگر نیز تعریف شده است: sit() و roll_over(). چون این متدها برای اجرا شدن به اطّلاعات اضافی نیاز ندارند آن را تنها با یک پارامتر، self، تعریف میکنیم. نمونههایی که بعداً میسازیم به این متدها دسترسی خواهند داشت. به بیانِ دیگر آنها میتوانند بنشینند یا غلت بزنند. فعلاً sit() و roll_over() کار زیادی نمیکنند. آنها تنها پیامی چاپ میکنند که سگ در حال نشستن یا غلت زدن است. امّا این مفهوم را میتوان به وضعیّتهای واقعی بسط داد: اگر این کلاس بخشی از یک بازی رایانهای واقعی بود، این متدها کدی را شامل میشدند که باعث میشد سگی انیمیشنی بنشیند یا غلت بزند. اگر این کلاس برای کنترل کردن رباتی نوشته بود، این متدها حرکتهایی را طوری هدایت میکردند که به نشستن یا غلت زدن سگِ رباتی منجر میشوند.
ساختن یک نمونه از یک کلاس
به کلاس به عنوان مجموعهای دستورالعملها برای چگونگی ساختنِ یک نمونه نگاه کنید. کلاس Dog مجموعهای از دستورالعملهاست که به پایتون میگوید چگونه نمونههای جداگانهای بسازد که هر کدام بیانگر سگِ خاصّی هستند.
اکنون نمونهای که نمایندهی سگ خاصّی باشد میسازیم:
classDog:
--snip--my_dog = Dog('Willie', 6)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
کلاس Dog در اینجا همانی است که در مثال قبلی تعریف کردیم. از پایتون میخواهیم که سگی به نام 'Willie' با ۶ سال سن بسازد. هنگامی که پایتون این خط را میخواند، متد __init__() را در Dog با نشانوندهای 'Willie' و 6 فراخوانی میکند. متد __init__() نمونهای بیانگر این سگِ خاص میسازد و مشخّصههای name و age را با توجه به مقادیرِ ارائه شده، تعیین میکند. سپس پایتون نمونهای را که بیانگر این سگ است برمیگرداند. این نمونه را به متغیّر my_dog نسبت میدهیم. قرارداد نامگذاری اینجا سودمند است: معمولاً میتوانیم فرض کنیم که نامهایی مانند Dog که حرف اوّل آنها بزرگ است به یک کلاس و آنهایی که با حروف کوچک نوشته میشوند، مانند my_dog، به یک نمونه از آن کلاس اشاره دارند.
دسترسی به مشخّصهها
برای دسترسی به مشخّصههای یک نمونه، از نمادگذاریِ نقطه استفاده میکنید. به این ترتیب به مقدار مشخّصهی name از نمونهی my_dog دسترسی پیدا میکنیم:
در پایتون نماد نقطه بسیار به کار میرود. این دستور نشان میدهد که چگونه پایتون مقدار یک مشخّصه را پیدا میکند. اینجا پایتون به نمونهی my_dog نگاه میکند و سپس مشخّصهی name را که به آن وابسته است پیدا میکند. این همان مشخّصهای است که در کلاس Dog به عنوان self.name مورد ارجاع قرار گرفت. از همین رویکرد برای کار کردن با مشخّصهی age استفاده کردهایم.
خروجی این کد خلاصهای است که دربارهی my_dog میدانیم:
پس از ساختن نمونهای از کلاس Dog، میتوانیم از نماد نقطه برای فراخواندن هر کدام از متدهای تعریفشده در Dog استفاده کنیم. اکنون کاری میکنیم که سگ ما بنشیند یا غلت بزند:
برای فراخوانیِ یک متد، نامِ نمونه (در این مورد my_dog) و نام متدی را که میخواهید فراخوانی کنید بنویسید و بین آنها یک نقطه بگذارید. زمانی که پایتون my_dog.sit() را میخواند به دنبال متد sit() در کلاس Dog میگردد و کد آن را اجرا میکند. پایتون خط my_dog.roll_over() را به همین ترتیب تفسیر میکند.
اکنون ویلی آنچه را به او فرمان دادهایم اجرا میکند:
این نوع نوشتن بسیار سودمند است. زمانی که مشخّصهها و متدها نامهای گویایی داشته باشند، مانند name، age، sit() و roll_over()، میتوانیم به سادگی به آنچه کد قرار است انجام دهد پی ببریم هر چند قبلاً هرگز آن را ندیده باشیم.
ساختن چند نمونه
میتوانید هر تعداد نمونه که بخواهید از یک کلاس بسازید. بیاید سگ دومی به نام your_dog بسازیم:
classDog:
--snip--my_dog = Dog('Willie', 6)
your_dog = Dog('Lucy', 3)
print(f"My dog's name is {my_dog.name}.")
print(f"My dog is {my_dog.age} years old.")
my_dog.sit()
print(f"\nYour dog's name is {your_dog.name}.")
print(f"Your dog is {your_dog.age} years old.")
your_dog.sit()
در این مثال سگی به نام ویلی و سگی دیگر به نام لوسی ساختهایم. هر سگ نمونهی جداگانهی خود را با مجموعهی مشخّصههای مخصوص به خود دارد و قادر به انجام کارهای مشترکی است:
My dog's name is Willie.
My dog is 6 years old.
Willie is now sitting.
Your dog's name is Lucy.
Your dog is 3 years old.
Lucy is now sitting.
حتّی اگر از یک نام و سن برای سگ دوم استفاده میکردیم، باز هم پایتون یک نمونهی جداگانه از کلاس Dog میساخت. شما میتوانید هر تعداد نمونه که خواستید از یک کلاس بسازید، تا زمانی که به هر نمونه یک نام متغیّر یکتا بدهید یا نمونه جای یکتایی را در یک لیست یا دیکشنری اشغال کند.
خودآزمایی
۹-۱. رستوران: کلاسی به نام Restaurant بسازید. متد __init__() در Restaurant باید دو مشخّصه را ذخیره کند: restaurant_name و cuisine_type. متدی به نام describe_restaurant() بسازید که این دو دسته اطّلاعات را چاپ کند، متدی هم به نام open_restaurant() بسازید که پیامی برای باز کردنِ رستوران چاپ کند.
نمونهای از کلاس خود به نام restaurant بسازید. دو مشخّصه را جداگانه چاپ کنید و سپس دو متد آن را فرابخوانید.
۹-۲. سه رستوران: از کلاسی که در تمرین ۹-۱ تعریف کردید شروع کنید. سه نمونهی مختلف از این کلاس بسازید و describe_restaurant() را برای هر نمونه فراخوانی کنید.
۹-۳. کاربران: کلاسی به نام User بسازید. دو مشخّصه به نامهای first_name و last_name بسازید و سپس چند مشخّصهی دیگر که معمولاً در نمایهی یک کاربر ذخیره میشود به آن اضافه کنید. متدی به نام describe_user() بسازید که خلاصهای از اطّلاعات یک کاربر را چاپ میکند. متد دیگری به نام greet_user() بسازید که یک پیام احوالپرسی شخصی برای کاربر چاپ میکند.
چندین نمونه که هر کدام بیانگر کاربر متفاوتی است بسازید و برای هر کدام هر دو متد را فراخوانی کنید.
9.2 - کار کردن با کلاسها و نمونهها
میتوانید از کلاسها برای بیان بسیاری از شرایط در دنیای واقعی استفاده کنید. پس از این که کلاس را نوشتید، وقت زیادی صرف کار کردن با نمونههایی میکنید که از آن کلاس ساختهاید. یکی از اوّلین کارهایی که میخواهید انجام دهید تغییر دادن مشخّصههای یک نمونهی خاص است. شما میتوانید مشخّصههای یک نمونه را به طور مستقیم یا از طریق نوشتن متدهای ویژهای تغییر دهید.
کلاس Car
بیایید کلاس جدیدی بنویسیم که نمایانگر یک خودرو باشد. کلاس ما اطّلاعاتی دربارهی نوع خودرویی که با آن کار میکنیم ذخیره میکند و متدی دارد که این اطّلاعات را خلاصه میکند:
classCar:
"""A simple attempt to represent a car."""def__init__(self, make, model, year):
"""Initialize attributes to describe a car.""" self.make = make
self.model = model
self.year = year
defget_descriptive_name(self):
"""Return a neatly formatted descriptive name.""" long_name =f"{self.year}{self.manufacturer}{self.model}"return long_name.title()
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
متد __init__() را با پارامتر ابتدایی self تعریف میکنیم. همچنین سه پارامتر دیگر به آن میدهیم: make، model و year. متد __init__() این پارامترها را میگیرد و آنها را به مشخّصههایی که با نمونههای ساختهشده از این کلاس در ارتباطند، نسبت میدهد. زمانی که نمونهی جدیدی از کلاس Car میسازیم باید نام شرکت سازنده، مدل و سال ساخت آن را برای نمونهی خود تعیین کنیم.
متدی به نام get_descriptive_name() تعریف میکنیم که year، make و model را در یک رشته قرار میدهد که خودرو را با سادگی و آراستگی توصیف میکند. این متد ما را از چاپ کردن جداگانهی مقدار هر مشخّصه خلاص میکند. برای کار کردن با مقادیر مشخّصههای در این متد از self.make، self.model و self.year استفاده میکنیم. نمونهای از کلاس Car میسازیم و آن را به متغیّر my_new_car نسبت میدهیم. سپس get_descriptive_name() را فراخوانی میکنید تا نوع خودروی ما را مشخّص کند:
برای جالبتر کردن کلاس، مشخّصهای به آن اضافه میکنیم که با زمان تغییر کند. این مشخّصه مسافت پیموده شدهی کلّی خودرو را ذخیره میکند.
تعیین کردن مقداری پیشفرض برای یک مشخّصه
میتوان هنگامِ ساختنِ یک نمونه، مشخّصهای را بدون انتقال آن به صورت پارامتر تعریف کرد. این مشخّصهها میتوانند در متد __init__() تعریف شوند و در آنجا به آنها مقداری پیشفرض داده شود.
مشخّصهای به نام odometer_reading اضافه میکنیم که همیشه در ابتدا مقدار ۰ دارد. همچنین متدی به نام read_odometer() اضافه میکنیم که ما را در خواندن مقدار کیلومترشمار هر خودرو کمک میکند:
classCar:
def__init__(self, make, model, year):
"""Initialize attributes to describe a car.""" self.make = make
self.model = model
self.year = year
self.odometer_reading =0defget_descriptive_name(self):
--snip
defread_odometer(self):
"""Print a statement showing the car's mileage.""" print(f"This car has {self.odometer_reading} miles on it.")
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.read_odometer()
این بار پایتون برای ساختن نمونهی جدید متد __init__() را فرامیخواند که مقدار سازنده، مدل و سال را مانند مثال قبلی به عنوان مشخّصه ذخیره میکند. سپس پایتون مشخّصهای جدید به نام odometer_reading میسازد و به آن مقدارِ اوّلیّهی ۰ میدهد. همچنین متد جدیدی به نام read_odometer() دارید که خواندن کارکرد خودرو را ساده میکند.
تمام خودروها با کارکرد اوّلیّهی دقیقاً ۰ فروخته نمیشوند پس نیاز به روشی داریم تا مقدار این مشخّصه را تغییر دهیم.
تغییردادن مقدار مشخّصهها
مقدار یک مشخّصه را میتوانید به سه روش عوض کنید: تغییر دادن مستقیم از طریق یک نمونه، تعیین کردن مقدار از طریق یک متد یا افزایش دادن مقدار (افزودن مقداری به آن) به کمک یک متد. به هر یک از این رویکردها نگاهی میاندازیم.
تغییر دادن مستقیمِ مقدار یک مشخّصه
سادهترین راه برای تغییرِ مقدارِ یک مشخّصه این است که از طریق یک نمونه به طور مستقیم به آن دسترسی پیدا کنیم. اینجا مقدار کیلومترشمار را به طور مستقیم ۲۳ قرار میدهیم:
از نماد نقطه برای دسترسی به مشخّصهی odometer_reading خودرو استفاده کرده و مقدار آن را به طور مستقیم تعیین میکنیم. این خط به پایتون میگوید که نمونهی my_new_car را بگیر، مشخّصهی odometer_reading مربوط به آن را پیدا کن و مقدار آن را برابر با ۲۳ بگذار:
گاهی میخواهید مانندِ اینجا به مشخّصهها به طور مستقیم دسترسی پیدا کنیم امّا برخی اوقات متدی مینویسید که مقدار را برای شما بهروز کند.
تغییر دادن مقدار یک مشخّصه از طریق یک متد
داشتنِ متدهایی برای بهروزرسانی مشخّصههای خاصّی میتواند سودمند باشد. میتوانید به جای دسترسی مستقیم به مشخّصه، مقدار جدید را به متدی بدهیم که به صورت داخلی این بهروزرسانی را انجام میدهد.
مثالِ اینجا متدی به نام update_odometer() را نشان میدهد:
classCar:
--snip--defupdate_odometer(self, mileage):
"""Set the odometer reading to the given value.""" self.odometer_reading = mileage
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.update_odometer(23)
my_new_car.read_odometer()
تنها تغییرِ ایجادشده در Car افزوده شدنِ update_odometer() است. این متد مقدار کارکرد را گرفته و به مشخّصهی self.odometer_reading نسبت میدهد. update_odometer() را فراخوانی میکنیم و مقدار ۲۳ را به عنوان نشانوند به آن میدهیم (که به پارامتر mileage در تعریفِ متد مربوط است.) این متد مقدار کیلومترشمار را ۲۳ میکند و read_odometer() این مقدار را چاپ میکند:
میتوانیم متد update_odometer() را طوری گسترش دهیم که در هر بار تغییرِ مقدارِ کیلومترشمار، کاری انجام دهد. اجازه دهید منطقی کوچکی اضافه کنیم تا مطمئن شویم هیچکس مقدار کیلومترشمار را به عقب برنمیگرداند:
classCar:
--snip--defupdate_odometer(self, mileage):
"""
Set the odometer reading to the given value.
Reject the change if it attempts to roll the odometer back.
"""if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
اکنون update_odometer() پیش از تغییر دادنِ مشخّصه، مقدار جدید را بررسی میکند. اگر کارکرد جدید، mileage، بزرگتر یا مساوی کارکرد فعلی (self.odometer_reading) باشد، میتوانید مقدار فعلی کیلومترشمار را به مقدار جدید بهروزرسانی کنید. اگر مقدار جدید کمتر از مقدار فعلی باشد، اخطاری دریافت میکنید مبنی بر این که نمیتوانید مقدار کیلومترشمار را به عقب برگردانید.
افزایش مقدار مشخّصه از طریق یک متد
گاهی میخواهید به جای تعیین یک مقدار کاملاً جدید برای یک مشخّصه، مقدار آن را به میزان مشخّصی افزایش دهید. فرض کنید خودرویی کارکرده خریدهایم و در فاصلهی بین خریدن و سند زدن، ۱۰۰ مایل کار کرده است. متدی که اینجاست مقدار افزایشی را دریافت کرده و آن را به مقدار فعلی کیلومترشمار اضافه میکند:
classCar:
--snip--defupdate_odometer(self, mileage):
--snip--defincrement_odometer(self, miles):
"""Add the given amount to the odometer reading.""" self.odometer_reading += miles
my_used_car = Car('subaru', 'outback', 2015)
print(my_used_car.get_descriptive_name())
my_used_car.update_odometer(23_500)
my_used_car.read_odometer()
my_used_car.increment_odometer(100)
my_used_car.read_odometer()
متد جدید increment_odometer() تعداد مایل پیمودهشده را گرفته و آن را به self.odometer_reading اضافه میکند. یک خودروی کارکرده، my_used_car، ایجاد میکنیم. مقدار کیلومترشمار آن را با فراخوانی update_odometer() و انتقال مقدار 23_500 به آن، ۲۳،۵۰۰ میگذاریم. سپس increment_odometer() را صدا کرده و مقدار 100 را به آن میدهیم تا ۱۰۰ مایل پیمودهشده بین خریدن و ثبت خودرو را اضافه کند:
2015 Subaru Outback
This car has 23500 miles on it.
This car has 23600 miles on it.
میتوانیم به سادگی این متد را تغییر دهیم تا افزودن مقدارهای منفی را نپذیرد تا هیچکس نتواند از این تابع برای کم کردن مقدار کیلومترشمار استفاده کند.
نکته
این که کاربرانِ برنامهی شما چگونه مقدارهایی نظیر مقدار کیلومترشمار را بهروزرسانی میکنند، به کمک چنین متدهایی قابل کنترل است امّا هرکس که به برنامه دسترسی داشته باشد میتواند این مقدار را با دسترسی مستقیم به مشخّصهها مستقیماً دستکاری کند. امنیّت موثّر علاوه بر بررسیهای ابتدایی مانند بالا، توجّه بسیار بیشتری به جزئیات میطلبد.
خودآزمایی
۹-۴. تعداد مشتریها: از برنامهی خود در تمرین ۹-۱ شروع کنید. مشخّصهای به نام number_served با مقدار پیشفرض ۰ اضافه کنید. نمونهای به نام restaurant از این کلاس بسازید. تعداد مشتریهایی را که رستوران پذیرای آنها بوده چاپ کنید و سپس با تغییر این مقدار آن را دوباره چاپ کنید.
متدی به نام set_number_served() اضافه کنید که امکان تعیین تعداد مشتریها را فراهم میآورد. این متد را با عدد جدیدی فراخوانی کنید و مقدار را دوباره چاپ کنید.
متدی به نام increment_number_served() اضافه کنید که به شما امکان افزایش تعداد مشتریها را میدهد. این متد را با هر عددی که دوست دارید فرابخوانید که بیانگر تعداد مهمانهای پذیرفتهشده در یک روز کاری باشد.
۹-۵. تعداد تلاشها برای ورود: مشخّصهای به نام login_attempts به کلاس User در تمرین ۹-۳ اضافه کنید. متدی به نام increment_login_attempts() بنویسید که مقدار login_attempts را یکی افزایش میدهد. متد دیگری به نام reset_login_attempts() بنویسید که مقدار login_attempts را صفر میکند.
نمونهای از کلاس User بسازید و increment_login_attempts() را چندین بار صدا کنید. مقدار login_attempts را چاپ کنید تا مطمئن شوید مقدار آن افزایش یافته است و سپس reset_login_attempts() را فراخوانی کنید. مقدار login_attempts را بار دیگر چاپ کنید تا مطمئن شوید مقدار آن صفر شده است.
9.3 - ارثبری
همیشه نیاز نیست نوشتنِ یک کلاس 8584
را ازد85
. اگر کلاسی که در حال نوشتن آن هستید، خود نسخهای اختصاصیشده از کلاسی دیگر باشد که قبلاً نوشتهاید، میتوانید از ارثبری استفاده کنید. هنگامی که کلاسی از کلاس دیگری ارث میبرد، مشخّصهها و متدهای کلاس اوّل را دریافت میکند. به کلاس اوّلّیه و اصلی کلاس والد و به کلاس جدید کلاس فرزند گویند. کلاس فرزند میتواند یکی یا تمام مشخّصهها و متدهای کلاس والد را به ارث ببرد امّا در عین حال آزاد است که مشخّصهها و متدهای خود را نیز تعریف کند.
متد __init__() برای کلاس فرزند
زمانی که کلاس جدیدی را بر پایهی یک کلاس موجود مینویسید، اغلب میخواهید متد __init__() کلاس والد را فراخوانی کنید. این کار موجب مقداردهی اوّلیّهی تمام مشخّصههای تعریفشده در متد __init__() والد خواهد شد و آنها را در کلاس فرزند در دسترس قرار میدهد.
به عنوان یک مثال بیایید یک خودروی الکتریکی را مدل کنیم. خودروی الکتریکی تنها نوعی خاصّی خودرو است بنابراین میتوانیم کلاس جدید ElectricCar را بر روی کلاس Car، که پیش از این نوشتهایم، بنا کنیم. پس از آن تنها کاری که باید بکنیم این است که مشخّصهها و رفتار خاصّ خودروهای الکتریکی را بنویسیم.
از ساختن نسخهی سادهای از کلاس ElectricCar شروع میکنیم که رفتار آن مشابه کلاس Car است:
classCar:
"""A simple attempt to represent a car."""def__init__(self, make, model, year):
self.make = make
self.model = model
self.year = year
self.odometer_reading =0defget_descriptive_name(self):
long_name =f"{self.year}{self.manufacturer}{self.model}"return long_name.title()
defread_odometer(self):
print(f"This car has {self.odometer_reading} miles on it.")
defupdate_odometer(self, mileage):
if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
defincrement_odometer(self, miles):
self.odometer_reading += miles
classElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""def__init__(self, make, model, year):
"""Initialize attributes of the parent class.""" super().__init__(make, model, year)
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
کلاس والد باید در فایل فعلی و پیش از تعریف کلاس فرزند در همان فایل ظاهر شود. کلاس فرزند، ElectricCar را تعریف میکنیم. نام کلاس والد باید هنگام تعریف کلاسِ فرزند، داخلِ پرانتز آورده شود. متد __init__() اطّلاعات لازم را برای ساختن یک نمونه از Car میگیرد.
تابع super() تابعی ویژه است که امکان فراخوانی متدی را از کلاس 8786
ی87
آورد. این خط به پایتون میگوید که متد __init__() را از Car فراخوانی کند، تا به یک نمونهی ElectricCar تمام مشخّصههای تعریفشده در آن متد داده شود. نام super از قراردادی میآید که در آن به کلاس والد ابرکلاس و به کلاس فرزند زیرکلاس گویند.
اکنون پس از ساختنِ یک خودروی الکتریکی بر اساس اطّلاعاتِ خودروی معمولی، بررسی میکنیم که آیا ارثبری به خوبی کار میکند یا نه. نمونهای از کلاس ElectricCar میسازیم و آن را به my_tesla نسبت میدهیم. این خط متد __init__() تعریفشده در ElectricCar را فراخوانی میکند که خود به پایتون میگوید که متد __init__() تعریفشده در کلاس والد Car را صدا کند. نشانوندهای 'tesla'، 'model s' و 2019 را ارائه میکنیم.
به جز __init__() هیچ مشخّصه یا متدی وجود ندارد که مخصوصِ یک خودروی الکتریکی باشد. تا اینجا فقط مطمئن میشویم که خودروی الکتریکی رفتارهای متناسب Car را دارد یا نه:
نمونهی ElectricCar درست مانند یک نمونهی Car کار میکند، بنابراین اکنون تعریف کردنِ متدها و مشخّصههای مختصّ خودروهای الکتریکی را آغاز میکنیم.
تعریف کردن مشخّصهها و متدهای کلاس فرزند
به محضِ ساختن یک کلاس فرزند که از کلاس والدی ارث میبرد، میتوانید هر مشخّصه یا متد جدیدی را که برای تمایز ایجاد کردن بین کلاس فرزند و والد نیاز است، اضافه کنید.
بیاید مشخّصهای را که مختصّ خودروهای الکتریکی است (مثلاً باتری) و متدی که این مقدار این مشخّصه را گزارش میکند، اضافه کنیم. ظرفیّت باتری و متدی را برای بیان وضعیّت باتری، ذخیره میکنیم:
classCar:
--snip--classElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""def__init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
""" super().__init__(make, model, year)
self.battery_size =75defdescribe_battery(self):
"""Print a statement describing the battery size.""" print(f"This car has a {self.battery_size}-kWh battery.")
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.describe_battery()
مشخّصهی جدید self.battery_size را افزوده و مقدار اوّلیّهی آن را ۷۵ گذاشتهایم. این مشخّصه به تمام نمونههایی که از کلاس ElectricCar ساخته میشوند مربوط خواهد بود امّا در هیچ نمونهای از Car وجود نخواهد داشت. همچنین متدی به نام describe_battery() اضافه میکنیم که اطّلاعات مربوط به باتری را چاپ میکند. زمانی که این متد را فراخوانی میکنیم توضیحی دریافت میکنیم که مختصّ یک خودروی الکتریکی است:
هیچ محدودیّتی برای میزانِ اختصاصی کردن کلاس ElectricCar وجود ندارد. میتوانید هر تعداد مشخّصه یا متد که نیاز دارید اضافه کنید تا یک خودروی الکتریکی را با هر درجهای از دقت که میخواهید مدل کنید. هر مشخّصه یا متدی که میتواند به هر خودرویی تعلّق داشته باشد و مخصوص خودروی الکتریکی نیست، باید به جای کلاس ElectricCar به کلاس Car اضافه شود، تا این که هر کس از کلاس Car استفاده میکند به این قابلیّت هم دسترسی داشته باشد و کلاس ElectricCar تنها شامل کدی باشد که بیانگر اطّلاعات و رفتارهای مختصّ خودروهای الکتریکی باشد.
بازتعریف 88
ِ متدهای کلاس والد
هنگام مدل کردن چیزی با کلاس فرزند، میتوانید هر کدام از متدهای کلاس والد را که مطابق مقصود شما نیست، بازتعریف کنید. برای این منظور، در کلاس فرزند متدی همنام با متد کلاس والد (که میخواهید آن را بازتعریف کنید) تعریف میکنید. پایتون به متد کلاس والد اهمّیّتی نمیدهد و توجّه خود را معطوفِ متدی میکند که در کلاس فرزند تعریف کردهاید.
فرض کنید کلاس Car متدی به نام fill_gas_tank() دارد. این متد برای خودروهای تماماً الکتریکی بیمعناست بنابراین ممکن است بخواهید آن را بازتعریف کنید. یک راه برای این کار چنین است:
classElectricCar(Car):
*--snip--*deffill_gas_tank(self):
"""Electric cars don't have gas tanks.""" print("This car doesn't need a gas tank!")
اکنون اگر کسی بخواهد متد fill_gas_tank() را برای یک خودروی الکتریکی فراخوانی کند، پایتون از متد fill_gas_tank() در کلاس Car صرف نظر کرده و به جای آن این کد را اجرا میکند. هنگامِ استفاده از ارثبری میتوانید کاری کنید که کلاس فرزند چیزهایی را که از کلاس والد نیاز دارید حفظ کند و هر آنچه را نمیخواهید بازتعریف کنید.
نمونهها در قالب مشخّصهها
زمانی که چیزی را در دنیای واقعی در کدتان مدل میکنید، ممکن است دریابید که جزئیات بسیار زیادی را به یک کلاس اضافه میکنید. متوجّه میشوید که فهرست مشخّصهها و متدها در حال بزرگ شدن است و فایلهای شما طولانیتر میشوند. در چنین وضعیّتهایی ممکن است تشخیص دهید که بخشی از یک کلاس میتواند خود به عنوان کلاسی جدا نوشته شود. میتوانید کلاس بزرگ خود را به چند کلاس کوچکتر بشکنید که با هم کار میکنند.
برای مثال زمانی که به افزودن جزئیات به کلاس ElectricCar ادامه میدهید، ممکن است متوجّه شوید که مشخّصهها و متدهای زیادی را مختصّ باتری اضافه کردهاید. زمانی که متوجه چنین وضعیتی میشویم، میتوانیم کار را متوقّف کنیم و آن مشخّصهها و متدها را به کلاسی جداگانه به نام Battery منتقل کنیم. سپس از نمونهی Battery به عنوان مشخّصهای در کلاس ElectricCar استفاده میکنیم:
classCar:
*--snip--*classBattery:
"""A simple attempt to model a battery for an electric car."""def__init__(self, battery_size=75):
"""Initialize the battery's attributes.""" self.battery_size = battery_size
defdescribe_battery(self):
"""Print a statement describing the battery size.""" print(f"This car has a {self.battery_size}-kWh battery.")
classElectricCar(Car):
"""Represent aspects of a car, specific to electric vehicles."""def__init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
""" super().__init__(make, model, year)
self.battery = Battery()
my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
کلاس جدیدی به نام Battery تعریف میکنیم که از هیچ کلاسی چیزی به ارث نمیبرد. متد __init__() علاوه بر self یک پارامتر دارد، battery_size. این پارامتر اختیاری است و اگر مقداری برای آن ارائه نشود ظرفیّت باتری را برابر ۷۵ میگذارد. متد describe_battery() هم به این کلاس منتقل شده است.
اکنون به کلاس ElectricCar مشخّصهای به نام self.battery اضافه میکنیم. این خط به پایتون میگوید که نمونهای جدید از Battery بساز (که چون مقداری برای آن تعیین نکردهایم ظرفیّت پیشفرض آن ۷۵ است) و این نمونه را به مشخّصهی self.battery نسبت بده. این اتّفاق هر بار که متد __init__() فراخوانده میشود، رخ میدهد؛ اکنون هر نمونهی ElectricCar یک نمونهی Battery هم دارد که به طور خودکار ساخته میشود.
یک خودروی الکتریکی ساخته و آن را به متغیّر my_tesla نسبت میدهیم. زمانی که میخواهیم دربارهی باتری توضیح دهیم، باید از طریق مشخّصهی battery خودرو عمل کنیم:
با این خط پایتون میفهمد که باید به نمونهی my_tesla نگاه کند، مشخّصهی battery آن را بیاید و متد describe_battery() را اجرا کند که به نمونهی Battery ذخیرهشده در این مشخّصه مربوط است.
این به نظر زیادهکاری میآید امّا اکنون میتوانید هر چهقدر جزئیات که بخواهیم به باتری اضافه کنیم بدون این که کلاس ElectricCar را شلوغ و درهمریخته کنیم. حالا متد دیگری به Battery اضافه میکنیم که مسافت قابل طی کردن با این ظرفیّت باتری را گزارش میکند:
classCar:
--snip--classBattery:
--snip--defget_range(self):
"""Print a statement about the range this battery provides."""if self.battery_size ==75:
range =260elif self.battery_size ==100:
range =315 print(f"This car can go about {range} miles on a full charge.")
classElectricCar(Car):
--snip--my_tesla = ElectricCar('tesla', 'model s', 2019)
print(my_tesla.get_descriptive_name())
my_tesla.battery.describe_battery()
my_tesla.battery.get_range()
متد جدید get_range() تحلیل سادهای انجام میدهد. اگر ظرفیّت باتری ۷۵ کیلوواتساعت باشد، فاصلهی قابل پیمایش را ۲۶۰ مایل و اگر ۱۰۰ کیلوواتساعت باشد برابر با ۳۱۵ مایل میگذارد. سپس مقدار را گزارش میکند. هرگاه بخواهیم این متد را فراخوانی کنیم، باید دوباره آن را از طریق مشخّصهی battery خودرو صدا کنیم.
خروجی بسته به ظرفیّت باتری، مسافتی را که خودرو میتواند طی کند گزارش میکند:
2019 Tesla Model S
This car has a 75-kWh battery.
This car can go about 260 miles on a full charge.
مدل کردن اشیای دنیای واقعی
همزمان که مدل کردن چیزهای پیچیدهتر مانند خودروهای الکتریکی را شروع میکنید، با پرسشهای جالبتری سر و کار خواهید داشت. آیا مسافت قابل پیمایش ویژگیِ خودروی الکتریکی است، یا باتری یا خودرو؟ اگر تنها یک خودرو را توصیف کنیم احتمالاً برقرار کردن ارتباط بین متد get_range() و کلاس Battery خوب است. امّا اگر بخواهیم کل خطّ تولید یک کارخانه را توصیف کنیم، ممکن است get_range() را به کلاس ElectricCar منتقل کنیم. متد get_range() پیش از محاسبهی مسافت همچنان ظرفیّت باتری را در نظر میگیرد امّا مسافت قابل پیمایش یک نوع خاصّ خودرو را که با آن در ارتباط است گزارش میدهد. به جای این کار، از سوی دیگر، میتوانیم ارتباط را بین متد get_range() و باتری برقرار کنیم امّا پارامتری مانند car_model را به آن بدهیم. در نتیجه متد get_range() مسافت را بر اساس ظرفیّت باتری و مدل خودرو گزارش خواهد کرد.
همزمان با رشد و پیشرفت شما به عنوان یک برنامهنویس، چنین مسائلی شما را به نقطهی جالبی میرسانند. زمانی که با چنین پرسشهایی سر و کله میزنید، به جای تمرکزِ صرف بر دستورها و شیوهی برنامهنویسی، در یک سطح منطقی بالاتر فکر میکنید. شما به پایتون فکر نمیکنید بلکه به این میاندیشید که چگونه مسائل دنیای واقعی را مدل کنید. زمانی که به اینجا رسیدید متوجّه میشوید که اغلب هیچ رویکرد کاملاً درست یا غلطی برای مدلسازی وضعیتهای دنیای واقعی وجود ندارد. برخی رویکردها از دیگر گزینهها کارآمدترند امّا پیدا کردن کارآمدترین نمونه کار میبرد. اگر کد شما آنگونه که میخواهید کار میکند، کار شما درست است! از این که گاهی خود را در حال قطعه قطعه کردن کلاسها و بازنویسی چندبارهی آنها با رویکردهای مختلف میبینید، ناامید نشوید. همهی افراد این فرآیند را برای رسیدن به روش نوشتن کد دقیق و کارآمد طی میکنند.
خودآزمایی
۹-۶. دکهی بستنیفروشی: دکهی بستنیفروشی را نوعی رستوران در نظر بگیرید. کلاسی به نام IceCreamStand تعریف کنید که از کلاس Restaurant که در تمرین ۹-۱ یا تمرین ۹-۴ ارث میبرد. هر دو کلاس کار میکنند؛ موردی را انتخاب کنید که بیشتر دوست دارید. مشخّصهای به نام flavors اضافه کنید که لیستی از طعمهای بستنی را ذخیره کند. متدی بنویسید که این طعمها را نمایش دهد. نمونهای از IceCreamStand بسازید و این متد را فراخوانی کنید.
۹-۷. مدیر: مدیر سیستم نوع خاصّی کاربر است. کلاسی به نام Admin بنویسید که از کلاس User (تمرین ۹-۳ یا تمرین ۹-۵) ارث ببرد. مشخّصهای به نام privileges (امتیازات) اضافه کنید که لیستی از رشتهها مانند "can add post"، "can delete post"، "can ban user" و … باشد. متدی به نام show_privileges() بنویسید که مجموعهی امتیازات مدیر را فهرست کند. نمونهای از Admin بسازید و متد خود را فرابخوانید.
۹-۸. امتیازات: کلاس جداگانهای به نام Privileges بسازید. این کلاس باید یک مشخّصه داشته باشد، privileges، که لیستی از رشتههای شرح دادهشده در تمرین ۹-۷ در آن ذخیره شود. متد show_privileges() را به این کلاس منتقل کنید. نمونهای از Privileges را به عنوان یک مشخّصه در کلاس Admin تعریف کنید. نمونهای جدید از Admin بسازید و از متد خود برای نمایش امتیازات مدیر بهره بگیرید.
۹-۹. ارتقای باتری: از نسخهی نهایی electric_car.py در این بخش استفاده کنید. متدی به نام upgrade_battery() به کلاس Battery اضافه کنید. این متد باید ظرفّیت باتری را بررسی کند و آن را برابر با ۱۰۰ بگذارد (البته اگر قبلاً این مقدار را نداشته است.) خودرویی الکتریکی با ظرفیّت باتری پیشفرض بسازید، یک بار get_range() را فراخوانی کنید. پس از ارتقای باتری به کمک متد خود، بار دیگر get_range() را صدا کنید. باید افزایشی در مسافت قابل پیمایش خودرو مشاهده کنید.
9.4 - وارد کردن کلاسها
بیایید ماژولی بسازیم که تنها شامل کلاس Car باشد. اینجا به یک مشکل ظریف در نامگذاری برمیخوریم: ما قبلاً در این فصل فایلی به نام car.py داشتهایم امّا این ماژول هم باید car.py نامگذاری شود زیرا دربردارندهی کدی بیانگر خودروست. این مشکل نامگذاری را اینگونه حل میکنیم: کلاس Car را در ماژولی به نام car.py ذخیره میکنیم، سپس آن را به جای فایل car.py قبلی میگذاریم. از این به بعد هر برنامهای که از این ماژول استفاده میکند به نام فایل ویژهتری نیاز دارد، مثلاً my_car.py. در اینجا، در car.py تنها باید کد کلاس Car را ببینیم:
"""A class that can be used to represent a car."""classCar:
"""A simple attempt to represent a car."""def__init__(self, make, model, year):
"""Initialize attributes to describe a car.""" self.make = make
self.model = model
self.year = year
self.odometer_reading =0defget_descriptive_name(self):
"""Return a neatly formatted descriptive name.""" long_name =f"{self.year}{self.manufacturer}{self.model}"return long_name.title()
defread_odometer(self):
"""Print a statement showing the car's mileage.""" print(f"This car has {self.odometer_reading} miles on it.")
defupdate_odometer(self, mileage):
"""
Set the odometer reading to the given value.
Reject the change if it attempts to roll the odometer back.
"""if mileage >= self.odometer_reading:
self.odometer_reading = mileage
else:
print("You can't roll back an odometer!")
defincrement_odometer(self, miles):
"""Add the given amount to the odometer reading.""" self.odometer_reading += miles
docstringای در سطح ماژول تعریف کردهایم که به طور خلاصه محتویّات ماژول را شرح میدهد. شما باید برای هر ماژولی که میسازید یک docstring بنویسید.
اکنون فایلی جداگانه به نام my_car.py میسازیم. این فایل کلاس Car را وارد کرده و نمونهای از آن را میسازد:
from car import Car
my_new_car = Car('audi', 'a4', 2019)
print(my_new_car.get_descriptive_name())
my_new_car.odometer_reading =23my_new_car.read_odometer()
عبارت import به پایتون میگوید که ماژول car را باز و کلاس Car را وارد کن. حال میتوانیم از کلاس Car استفاده کنیم؛ انگار که در همین فایل تعریف شده باشد. خروجی مشابه چیزی است که قبلتر دیدیم:
وارد کردن کلاسها روش موثّری برای برنامهنویسی است. تصوّر کنید که در صورت آوردنِ کل کلاس Car در فایل برنامه، این فایل چهقدر طولانی میشد. زمانی که به جای این کار، کلاس را به ماژولی منتقل و سپس آن ماژول را وارد میکنید همان قابلیّتها را در اختیار دارید امّا فایل برنامهی اصلی شما تمیزتر و خواندنِ آن راحتتر باقی میماند. همچنین شما بخش مهمّی از منطق را در فایلهای جداگانه ذخیره میکنید؛ به محض این که کلاسهایتان مطابق آنچه انتظار دارید کار کردند، آنها را در آن فایلها رها میکنید و در عوض در برنامهی اصلی خود روی منطقِ سطحِ بالاتر تمرکز میکنید.
ذخیره کردن چند کلاس در یک ماژول
شما میتوانید هر تعداد کلاس که نیاز دارید در یک ماژول ذخیره کنید، با این حال کلاسهای هر ماژول باید به نحوی به یکدیگر مربوط باشند. کلاسهای Battery و ElectricCar هر دو نمایانگر خودروها هستند بنابراین آنها را به ماژول car.py اضافه میکنیم.
"""A set of classes used to represent gas and electric cars."""classCar:
--snip--classBattery:
"""A simple attempt to model a battery for an electric car."""def__init__(self, battery_size=70):
"""Initialize the battery's attributes.""" self.battery_size = battery_size
defdescribe_battery(self):
"""Print a statement describing the battery size.""" print(f"This car has a {self.battery_size}-kWh battery.")
defget_range(self):
"""Print a statement about the range this battery provides."""if self.battery_size ==75:
range =260elif self.battery_size ==100:
range =315 print(f"This car can go about {range} miles on a full charge.")
classElectricCar(Car):
"""Models aspects of a car, specific to electric vehicles."""def__init__(self, make, model, year):
"""
Initialize attributes of the parent class.
Then initialize attributes specific to an electric car.
""" super().__init__(make, model, year)
self.battery = Battery()
اکنون میتوانیم فایل جدیدی به نام my_electric_car.py بسازیم، کلاس ElectricCar را وارد کنیم و یک خودروی الکتریکی بسازیم:
2019 Tesla Model S
This car has a 75-kWh battery.
This car can go about 260 miles on a full charge.
وارد کردن چند کلاس از یک ماژول
شما میتوانید هر تعداد کلاسی که نیاز داشتید در یک فایل برنامه وارد کنید. اگر بخواهیم در یک فایل یک خودروی معمولی و یک خودروی الکتریکی بسازیم، باید هر دو کلاس Car و ElectricCar را وارد کنیم:
from car import Car, ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
با جدا کردن کلاسها با کاما میتوانید چند کلاس را از یک ماژول وارد کنید. پس از وارد کردن کلاسهای ضروری این امکان را دارید که هر تعداد نمونه از کلاسهای مورد نیاز خود بسازید.
در این مثال یک خودروی معمولی Volkswagen Beetle و یک خودروی الکتریکی Tesla Roadster میسازیم:
علاوه بر این روشها میتوانید کلّ یک ماژول را وارد کنید و به کلاسهای مورد نیاز به کمک علامت نقطه دسترسی پیدا کنید. این رویکرد ساده است و به کدی منجر میشود که خواندن آن آسان است. از آنجاییکه هر فراخوانی برای ساختن نمونهی یک کلاس باید شامل نام ماژول باشد، در این روش به تداخل نام با نامهای به کار رفته در فایل فعلی برنمیخورید.
وارد کلّ ماژول car و ساختن یک خودروی معمولی و الکتریکی به این شکل است:
کلّ ماژول car را وارد میکنیم. پس از آن از طریق دستور module_name.ClassName به کلاسهای آن دسترسی پیدا میکنیم. دوباره یک Volkswagen Beetle و یک Tesla Roadster میسازیم.
وارد کردن تمام کلاسهای یک ماژول
شما میتوانید تمام کلاسهای یک ماژول را به کمک دستور زیر وارد کنید:
این روش به دو دلیل پیشنهاد نمیشود. یکم، خوب است که قادر باشیم با خواندن عبارتهای import در بالای یک فایل درکی کلی از کلاسهایی که برنامه استفاده میکند پیدا کنیم. با این رویکرد واضح نیست چه کلاسهایی را از ماژول گرفتهایم. این رویکرد همچنین ممکن است به ایجاد تداخل و سردرگمی در نامهای درون فایل شود. اگر سهواً کلاسی همنام با چیزی دیگر در فایل برنامهی خود وارد کنید، ممکن است به خطاهایی بخورید که تشخیص آنها مشکل باشد. اگر چه این مورد، رویکرد توصیهشدهای نیست امّا اینجا نشان داده شده چون ممکن است آن را در جایی در کد دیگران ببینید.
اگر نیاز دارید که تعداد زیادی کلاس را از ماژولی وارد کنید، بهتر است کل ماژول را وارد کرده و دستور module_name.ClassName را به کار بگیرید. هرچند نام تمام کلاسها را بالای فایل خود نمیبینید امّا هنگامی که ماژول در جایی از برنامه استفاده شود، نام آن کاملاً مشخّص است. همچنین تداخلهای بالقوّهی نامگذاری که هنگام وارد کردن تمام کلاسهای یک ماژول به آن برمیخورید، رخ نخواهد داد.
وارد کردن یک ماژول در ماژول دیگر
گاهی ممکن است بخواهید که کلاسهای خود را بین چند ماژول پخش کنید تا از طولانی شدن بیش از اندازهی یک فایل و ذخیره شدن چند کلاس نامربوط در یک ماژول جلوگیری کنید. هنگام ذخیره کردن کلاسها در چند ماژول ممکن است دریابید که کلاسی در یک ماژول به کلاسی در ماژول دیگری وابسته است. زمانی که چنین چیزی رخ دهد میتوانید کلاس مورد نیاز را از ماژول اوّل وارد کنید.
برای مثال کلاس Car را در یک ماژول و کلاسهای ElectricCar و Battery را در ماژولی جدا ذخیره میکنیم. ماژول جدیدی به نام electric_car.py میسازیم و آن را جایگزینِ فایل electric_car.py که پیش از این ساخته بودیم میکنیم و تنها دو کلاس Battery و ElectricCar را درون آن کپی میکنیم:
"""A set of classes that can be used to represent electric cars."""from car import Car
classBattery:
--snip--classElectricCar(Car):
--snip--
کلاس ElectricCar نیاز به دسترسی به کلاس والد خود Car دارد، بنابراین Car را مستقیماً در ماژول وارد میکنیم. اگر این خط را فراموش کنیم، هنگام وارد کردن ماژول electric_car اعلام خطا میکند. همچنین باید ماژول Car را بهروزرسانی کنیم تا تنها شامل کلاس Car باشد:
from car import Car
from electric_car import ElectricCar
my_beetle = Car('volkswagen', 'beetle', 2019)
print(my_beetle.get_descriptive_name())
my_tesla = ElectricCar('tesla', 'roadster', 2019)
print(my_tesla.get_descriptive_name())
Car را از ماژول خود و ElectricCar را از ماژول مربوطهاش وارد میکنیم. سپس یک خودروی معمولی و یک خودروی الکتریکی میسازیم. هر دو نوع خودرو به درستی ساخته شدهاند:
چنانکه در فصل ۸ دیدید هنگام استفاده از ماژولها نامهای مستعار میتوانند به مرتبسازی کد پروژههای شما کمک کنند. میتوانید از نامهای مستعار هنگام وارد کردن کلاسها هم استفاده کنید.
به عنوان یک مثال برنامهای را در نظر بگیرید که میخواهید در آن تعدادی خودروی الکتریکی بسازید. نوشتن و خواندن چندبارهی ElectricCar ممکن است خستهکننده باشد. شما میتوانید در عبارت وارد کردن نام مستعاری به ElectricCar بدهید:
همچنانکه میبینید پایتون برای ساختار دادن به کد یک پروژهی بزرگ چندین گزینه پیش روی شما میگذارد. دانستن تمام این امکانها مهم است تا بتوانید بهترین راه را برای نظمدهی به پروژههای خوب بیابید و در عین حال پروژههای دیگر افراد را هم درک کنید.
زمانی که تازه شروع میکنید ساختار کد خود را ساده نگه دارید. همهی کارها را در یک فایل انجام دهید و زمانی که متوجّه شدید که همه چیز درست کار میکند کلاسها را به ماژولهای جداگانه منتقل کنید. اگر به چگونگی ارتباط ماژولها و فایلها علاقه دارید، کلاسهای خود را در ابتدای پروژه در ماژولها قرار دهید. رویکردی را بیابید که به شما امکان نوشتن کدی میدهد که کار میکند و از آن شروع کنید.
خودآزمایی
۹-۱۰. رستورانهای واردشده: از آخرین کلاس Restaurant خود استفاده کنید و آن را در یک ماژول ذخیره کنید. فایلی جدا بسازید که Restaurant را وارد میکند. نمونهای از این کلاس بسازید و از متدهای آن استفاده کنید تا نشان دهید که عبارت import به درستی کار میکند.
۹-۱۱. کلاس مدیر واردشده: از کار خود در تمرین ۹-۸ شروع کنید. کلاسهای User، Privileges و Admin را در یک ماژول ذخیره کنید. فایلی جدا بسازید، نمونهای از Admin ایجاد کنید و show_privileges() را فراخوانی کنید تا معلوم شود همه چیز درست کار میکند.
۹-۱۲. چند ماژول: کلاس User را در یک ماژول و دو کلاس Privileges و Admin را در ماژولی دیگر ذخیره کنید. در فایلی جدا نمونهای از Admin بسازید و show_privileges() را صدا کنید تا نشان دهید که همه چیز همچنان درست کار میکند.
9.5 - کتابخانهی استاندارد پایتون و شکلدهی به کلاسها
کتابخانهی استاندارد پایتون مجموعهای از ماژولهاست که در هر نسخهی نصبشدهی پایتون یافت میشود. اکنون که به طور مقدّماتی از نحوهی کار تابعها و کلاسها اطّلاع یافتهاید، میتوانید بهکارگیری ماژولهایی مانند این را که دیگر برنامهنویسان نوشتهاند شروع کنید. میتوانید با وارد کردن یک عبارت import ساده در بالای فایل خود از هر تابع یا کلاسی از کتابخانهی استاندارد استفاده کنید. بیایید به یکی از ماژولها، random، نگاهی بیندازیم که در مدلسازی بسیاری از وضعیّتهای دنیای واقعی میتواند سودمند باشد.
یکی از تابعهای جالب در ماژول اعداد تصادفی randint() است. این تابع دو عدد صحیح را به عنوان نشانوند میگیرد و یک عدد صحیح تصادفی بین این دو عدد (و خود آنها) برمیگرداند.
اینجا نحوهی تولید عددی تصادفی بین ۱ و ۶ را میبینیم:
ماژول random نباید برای ساختن برنامههای کاربردی با محوریّت امنیّت استفاده شود امّا برای پروژههای سرگرمی و جذّاب به اندازهی کافی خوب است.
نکته
میتوانید ماژولها را از منابع خارجی هم بگیرید. چند نمونه از آنها را در بخش دوم کتاب خواهید دید جایی که برای تکمیل هر پروژه به ماژولهای خارجی نیاز دارید.
خودآزمایی
۹-۱۳. تاس: کلاسی به نام Die بسازید که مشخّصهای به نام sides (وجهها) دارد و مقدار اوّلیّهی آن ۶ است. متدی به نام roll_die() بنویسید که عددی تصادفی بین ۱ و تعداد وجههای تاس چاپ میکند. تاسی ششوجهی بسازید و آن را ۱۰ بار بیندازید.
یک تاس دهوجهی و بیستوجهی بسازید و هر کدام را ۱۰ بار بیندازید.
۹-۱۴. بختآزمایی: لیست یا چندتاییای از ۱۰ عدد و ۵ حرف بسازید. چهار عدد یا حرف را به طور تصادفی از لیست انتخاب کنید و پیامی چاپ کنید که شمارهی بلیت هرکس به این چهار حرف یا عدد بخورد برندهی جایزهای شده است.
۹-۱۵. تحلیل بختآزمایی: برای بررسی این که برنده شدن در بختآزماییای که مدل کردهاید، چهقدر میتواند سخت باشد میتوانید از یک حلقه استفاده کنید. لیست یا چندتاییای به نام my_ticket بسازید. حلقهای بنویسید که تا زمان برنده شدن بلیت شما به تولید دنبالهی اعداد و حروف ادامه دهد و شمارهی تعداد دفعات حلقه را نگه دارد. پیامی چاپ کنید که گزارش کند حلقه برای برنده شدن شما چند بار اجرا شده است.
۹-۱۶. ماژول هفتهی پایتون: یکی از منابع عالی برای کاویدن کتابخانهی استاندارد پایتون سایتی به نام Python Module of the Week است. به نشانی https://pymotw.com/ بروید و به فهرست آن نگاهی بیندازید. ماژولی را پیدا کنید که به نظرتان جالب میآید و دربارهی آن بخوانید، شاید بهتر باشد از ماژول random شروع کنید.
شکلدادن به کلاسها
تعداد کمی از نکات مربوط به شکلدهی کلاسها ارزش شفافسازی دارند به ویژه زمانی که برنامههای شما به تدریج پیچیدهتر میشوند.
نام کلاسها باید به صورت CamelCase نوشته شوند. برای این کار اوّلین حرف هر کلمه را بزرگ کنید و از زیرخطها استفاده نکنید. نام ماژولها و نمونهها باید با حروف کوچک و زیرخط بین واژگان نوشته شوند.
هر کلاس باید بلافاصله بعد از تعریف کلاس یک docstring داشته باشد. این docstring باید شرحی خلاصه از آنچه کلاس انجام میدهد باشد و برای نوشتن آن باید از همان قواعد نوشتن docstring در تابعها پیروی کنید. هر ماژول نیز باید docstringای داشته باشد که مواردِ استفادهی کلاسهای ماژول را شرح دهد.
همچنین میتوانید از خطهای خالی برای مرتّب کردن کدهای خود استفاده کنید امّا از آنها بیش از حد استفاده نکنید. در هر کلاس از یک خط خالی بین متدها و در هر ماژول از دو خط خالی برای جدا کردن کلاسها استفاده کنید.
اگر به وارد کردنِ همزمانِ ماژولی از کتابخانهی استاندارد پایتون و ماژولی که خود نوشتهاید نیاز دارید، عبارت مربوط به وارد کردن ماژول کتابخانهی استاندارد را در ابتدا بیاورید. سپس یک خط خالی اضافه کنید و عبارت وارد کردن ماژول خود را بنویسید. در برنامههایی که چندین عبارت وارد کردن دارند، این قرارداد باعث میشود که تشخیص این که ماژولهای مختلف بهکاررفته در برنامه از کجا آمدهاند سادهتر باشد.
خلاصه
در این فصل آموختید که چگونه کلاسهای خود را بنویسید. یاد گرفتید که چگونه به کمک مشخّصهها اطّلاعاتی را در کلاسها ذخیره کنید و چگونه با نوشتن متدها رفتاری را که میخواهید به کلاسهای خود بدهید. چگونه نوشتن متدهای __init__() را آموختید که دقیقاً با مشخّصههایی که میخواهید، نمونههایی از کلاسهای شما میسازند. چگونگی تغییر دادن مشخّصهها را به طور مستقیم و از طریق متدها دیدید. آموختید که ارثبری میتواند ساختن کلاسهایی را که به هم مربوطاند ساده کند و یاد گرفتید که چگونه از نمونههای یک کلاس به عنوان مشخّصههایی در کلاس دیگر استفاده کنید تا هر کلاس را ساده نگه دارید.
دیدید که چگونه ذخیره کردن کلاسها در ماژولها و وارد کردن آنها در فایلها (هر جا که مورد نیاز بودند) پروژههای شما را مرتّب نگه میدارد. آموختن دربارهی کتابخانهی استاندارد پایتون را آغاز کردید و مثالی بر پایهی ماژول random مشاهده کردید. در نهایت شکلدهی به کلاسهای خود را بر اساس قراردادهای پایتون آموختید.
در فصل ۱۰ کار کردن با فایلها را خواهید آموخت تا بتوانید کارهایی را که در یک برنامه کردهاید یا به کاربران اجازهی انجام دادهاید، ذخیره کنید و همچنین در مورد استثناها خواهید آموخت که کلاسی خاص در پایتون است که به شما در واکنش دادن به خطاهایی که رخ میدهند کمک میکند.
10 - فایلها و استثناها
10.1 - خواندن یک فایل
حال که بر مهارتهای مقدّماتیِ مورد نیاز برای نوشتنِ برنامههای سازمانیافته و با کاربریِ آسان تسلّط یافتهاید، زمانِ فکر کردن به این موضوع رسیده است که چگونه برنامههایتان را از این هم مناسبتر و قابل استفادهتر کنید. در این فصل کار کردن با فایلها را خواهید آموخت تا برنامههایتان بتوانند حجم زیادی از دادهها را به سرعت تحلیل کنند. مدیریّتِ خطاها را نیز میآموزید تا برنامهها هنگام برخورد با شرایط پیشبینینشده از کار نیفتند. دربارهی استثناها خواهید آموخت: شیءهای ویژهای که پایتون هنگام اجرای برنامه میسازد تا خطاهای رخداده را مدیریّت کند. همچنین دربارهی ماژول json خواهید آموخت که به کمک آن میتوانید اطّلاعات کاربر را ذخیره کنید تا با توقّف برنامه از بین نروند.
آموختنِ نحوهی کار کردن با فایلها و ذخیره کردنِ دادهها، استفاده از برنامههای شما را برای کاربران آسانتر میکند. کاربران میتوانند انتخاب کنند که چه دادههایی را در چه زمانی وارد کنند. افراد میتوانند برنامهی شما را اجرا کنند، قدری کار انجام دهند، آن را ببندند و مدّتی بعد کار خود را از همانجا دوباره ادامه دهند. مدیریّت استثناها به شما کمک میکند تا وضعیّتهایی را که فایلی وجود ندارد یا دیگر مشکلاتی را که میتوانند به از کار افتادنِ برنامهی شما منجر شوند، مدیریّت کنید. این کار برنامههای شما را هنگام برخوردن به دادههای نامناسب، مقاومتر میکند، چه این دادهها سهواً و اشتباهاً وارد شده باشند چه حاصلِ حملههای مخرّبی برای خراب کردنِ برنامههای شما باشند. با مهارتهایی که در این فصل میآموزید، برنامههای خود را کاربردیتر، کاراتر و پایدارتر میکنید.
حجم بسیار زیادی داده در فایلهای متنی موجود است. فایلهای متنی ممکن است شامل دادههای هواشناسی، ترافیکی، اقتصادی-اجتماعی، ادبیّات و … باشند. خواندن دادهها از یک فایل به ویژه در کاربردهای مربوط به تحلیلِ داده سودمند است، امّا در بسیاری از وضعیّتها که در آنها به تحلیل یا تغییرِ اطّلاعاتِ ذخیرهشده در یک فایل نیاز داشته باشید هم کاربردی است. برای مثال میتوانید برنامهای بنویسید که محتویّات یک فایل متنی را میخواند و آن را به شکلی بازنویسی میکند که در یک مرورگر قابل نمایش باشد.
اوّلین گام برای کار کردن با اطّلاعاتِ داخلِ یک فایل خواندنِ آن و انتقال آن به حافظه است. میتوانید کلّ یک فایل را یکجا بخوانید یا این که هر بار با یک خطّ آن کار کنید.
خواندن کامل یک فایل
برای آغاز کردن به فایلی نیاز داریم که چند خطّ متن داخلِ آن باشد. بیایید از فایلی شروع کنیم که در خود عدد پی را تا ۳۰ رقم اعشار دارد و هر ده رقم اعشار در یک خط قرار گرفته است:
برای اجرا کردن مثالهای پیشِ رو میتوانید این خطها را در یک ویرایشگر وارد کنید و آن را با نام pi_digits.txt ذخیره کنید یا این فایل را از منابع این کتاب به آدرس https://nostarch.com/pythoncrashcourse2e دریافت کنید. فایل را در همان پوشهای ذخیره کنید که برنامههای این فصل را نگه میدارید.
این برنامهای است که این فایل را باز میکند، آن را میخواند و محتویّات آن را روی صفحه چاپ میکند:
with open('pi_digits.txt') as file_object:
contents = file_object.read()
print(contents)
خطّ اوّل این برنامه حرف بسیاری برای گفتن دارد. با بررسی تابع open() شروع کنیم. برای کار کردن با یک فایل، حتّی برای چاپ کردن محتویّات آن، باید ابتدا آن را باز کنید تا به آن دسترسی داشته باشید. تابع open() تنها یک نشانوند میپذیرد: نام فایلی که میخواهید باز کنید. پایتون در پوشهای به دنبال این فایل میگردد که برنامهی در حالِ اجرای فعلی در آن ذخیره شده است. در این مثال file_reader.py در حال اجراست پس پایتون در پوشهای که این فایل ذخیره شده به دنبال فایل pi_digits.txt میگردد. تابع open() شیءای را برمیگرداند که نمایندهی این فایل است. در اینجا open('pi_digits.txt') شیءای را برمیگرداند که نمایندهی pi_digits.txt باشد. پایتون این شیء را به file_object نسبت میدهد که جلوتر در این برنامه با آن کار خواهیم کرد.
کلیدواژهی with بلافاصله بعد از این که دیگر نیازی به دسترسی به فایل نباشد، آن را میبندد. به این نکته دقّت کنید که در این برنامه open() را صدا کردهایم امّا close() را نه. میتوانستید فایل را با فراخوانی open() و close() باز و بسته کنید، امّا اگر اشکالی در برنامهی شما مانع اجرا شدنِ متد close() شود، ممکن است فایل هیچگاه بسته نشود. ممکن است بدیهی به نظر بیاید ولی بستنِ نامناسبِ فایلها میتواند موجبِ از بین رفتن یا آسیب دیدن اطّلاعات شود. و اگر close() را زودتر از موعد فرابخوانید، در جایی از برنامه خود را در حال کار کردن با یک فایل بسته خواهید یافت که خود موجب خطاهای بیشتر خواهد شد. فهمیدنِ زمانِ دقیقِ بستنِ یک فایل ممکن است همیشه ساده نباشد امّا با ساختاری که اینجا نشان داده شده پایتون خود این کار را برای شما خواهد کرد. تمامِ کاری که باید بکنید این است که فایل را باز کرده و کاری که میخواهید با آن انجام دهید و مطمئن باشید که پایتون هر گاه که کار اجرای قطعهی with به پایان رسید فایل را به طور خودکار خواهد بست.
پس از آنکه شیء فایلیِ نمایندهی pi_digits.txt در اختیار بود، برنامه با استفاده از متد read() در خطّ دوم، تمام محتویّات آن را خوانده و در یک رشتهی بلند به نام contents ذخیره میکند. زمانی که مقدار contents را چاپ میکنیم و تمام فایل متنی را دریافت میکنیم:
تنها تفاوت بین خروجی و فایلِ اصلی خطّ خالی اضافهای در آخر خروجی است. این خطّ خالی به این دلیل پدیدار شده که read() هنگام رسیدن به پایان فایل یک رشتهی خالی برمیگرداند؛ این رشتهی خالی به صورت یک خطّ خالی نمایش داده میشود. اگر بخواهید این خطّ خالی را حذف کنید میتوانید هنگام فراخوانی print()، rstrip() را به کار بگیرید:
زمانی که نام فایل سادهای مانند pi_digits.txt را به تابع open() میدهید پایتون در پوشهای به دنبال آن میگردد که فایلِ در حالِ اجرای فعلی در آن قرار دارد. (که همان فایلِ .py برنامهی شماست.)
بسته به نحوهی سازماندهی کار شما، گاهی فایلی که به90
دنبال باز کردنِ آن هستید در پوشهی فایل برنامهی شما وجود ندارد. برای مثال ممکن است شما فایل برنامهی خود را در پوشهای به نام python_work ذخیره کنید و در این پوشه، پوشهی دیگری به نام text_files بسازید تا بین فایلهای برنامه و فایلهای متنی تمایز قایل شوید. اگرچه text_files داخل python_work قرار دارد امّا اگر تنها نام فایلی را که در text_files قرار دارد، در open() وارد کنیم، کارگر نخواهد بود چون پایتون تنها در python_work جستوجو میکند و در همانجا متوقّف میشود؛ و داخلِ text_files را نمیگردد. برای این که پایتون در پوشهای به جز پوشهی محلّ ذخیرهسازی فایل برنامه به دنبال فایلی بگردد باید یک مسیر 91
فایل ارائه کنید که به پایتون بگوید یک محلّ خاص را در سیستم شما نگاه کند.
چون text_files داخل python_work قرار دارد میتوانید برای باز کردن فایلی در داخل آن، از یک مسیر فایل نسبی استفاده کنید. مسیر فایل نسبی به پایتون میگوید که محلّ واردشده را نسبت به پوشهای در نظر بگیر که محلِّ ذخیرهسازی فایلِ برنامهی در حالِ اجرای فعلی است و داخل آن را ببیند. برای مثال میتوانید به این صورت بنویسید:
with open('text_files/filename.txt') as file_object:
این خط از پایتون میخواهد که در پوشهی text_files به دنبال فایل .txt مطلوب بگردد و فرض کند که text_files داخل python_work قرار دارد.
نکته
سیستمهای ویندوزی برای نمایش مسیر 92
فایلها به جای اسلش (/) از بکاسلش (\) استفاده میکند، با این حال میتوانید در کد خود همچنان از اسلش استفاده کنید.
علاوه بر این، میتوانید صرف نظر از جایی که برنامهی فعلی ذخیره شده، به پایتون محلّ دقیق فایل را بدهید. به این مسیر فایل مطلق میگویند. زمانی که مسیر نسبی به کار نیامد میتوانید از مسیر مطلق استفاده کنید. برای مثال اگر text_files را در پوشهای به جز python_work قرار دهید (مثلاً پوشهای به نام other_files)، دادنِ مسیری مانند 'text_files/filename.txt' به open()، کار نخواهد کرد زیرا پایتون تنها داخل python_work دنبال آن خواهد گشت. اینجا نیاز دارید که مسیر کامل را بنویسید تا برای پایتون شفّاف باشد که کجا را بگردد.
مسیرهای مطلق معمولاً از مسیرهای نسبی طولانیترند پس خوب است که آنها را به متغیّری نسبت دهیم و سپس آن متغیّر را به open() بدهیم:
file_path ='/home/ehmatthes/other_files/text_files/filename.txt'with open(file_path) as file_object:
با بهکارگیری مسیرهای مطلق میتوانید فایلها را از هر جایی در سیستم خود بخوانید. فعلاً سادهترین کار ذخیره کردنِ فایلها در همان پوشهی فایلهای برنامه است؛ یا این که آنها را در پوشهای مانند text_files در همان پوشهی فایلهای برنامه ذخیره کنید.
نکته
اگر در یک مسیر فایل از بکاسلش استفاده کنید به خطا میخو93
رید چون بکاسلش در رشتهها در کاراکترهای گریز استفاده میشود. برای مثال در مسیرِ "C:\path\to\file.txt" دنبالهی \t به عنوان تب تفسیر میشود. اگر به بهکارگیری بکاسلشها نیاز داشتید باید خود آنها را با کاراکتر گریز به کار ببرید: "C:\\path\\to\\file.txt"
10.3 - خواندن خط به خط
اغلب هنگامِ خواندنِ یک فایل نیاز دارید هر خطّ آن را بررسی کنید. شاید به دنبال اطّلاعات خاصّی در فایل باشید یا بخواهید متن داخل آن را به شکلی تغییر دهید. برای مثال ممکن است بخواهید فایلی از دادههای هواشناسی را بخوانید و با خطهایی که در توضیح آب و هوای آن روز عبارت sunny (آفتابی) داشته باشد کاری انجام دهید. در گزارش اخبار ممکن است به دنبال خطهایی با برچسب <headline> باشید و به آن خط شکل خاصّی بدهید.
برای بررسی خط به خط یک فایل میتوانید با حلقهی for در شیء فایل حلقه بزنید:
filename ='pi_digits.txt'with open(filename) as file_object:
for line in file_object:
print(line)
در ۱ نام فایلی را که در حال خواندن آن هستیم به متغیّر filename نسبت میدهیم. این رویّهای مرسوم برای کار کردن با فایلهاست. چون متغیّر filename بیانگر یک فایل واقعی نیست—فقط رشتهای است که به پایتون جای فایل را نشان میدهد—میتوانید 'pi_digits.txt' را با نام فایل دیگری جابهجا کنید که میخواهید با آن کار کنید. پس از فراخوانی open() شیءای که بیانگر فایل است و محتویّات آن را به متغیّر file_object نسبت میدهیم (۲). دوباره از دستور with استفاده میکنیم تا به پایتون اجازه دهیم فایل را به طور مناسب باز و بسته کند. برای بررسی محتویّات فایل، در شیء فایل حلقه میزنیم و با هر خطّ فایل کار میکنیم (۳).
وقتی هر خط را چاپ میکنیم، به فاصلههای خالی بیشتری بین خطها برمیخوریم:
این خطهای خالی به این دلیل پدیدار میشوند که در فایل متنی در پایان هر خط یک کاراکتر خطّ جدید پنهان وجود دارد. تابع print هم که پس از هر بار فراخوانی یک خطّ جدید اضافه میکند، در نتیجه در پایان هر خط در مجموع به دو خطّ خالی برمیخوریم: یکی از فایل و یکی هم به خاطر print(). هنگام فراخوانی print() با بهکارگیری rstrip() برای هر خط این خطهای خالی اضافی حذف میشوند:
filename ='pi_digits.txt'with open(filename) as file_object:
for line in file_object:
print(line.rstrip())
اکنون بار دیگر خروجی مشابه محتویّات فایل میشود.
ساختن لیستی از خطهای یک فایل
زمانی که از with استفاده میکنید شیء فایلی که open() برمیگرداند تنها داخل همان بخش with() در دسترس است که آن را در برگرفته. اگر بخواهید دسترسی به محتویّات فایل را پس از قسمت with هم حفظ کنید، میتوانید در داخل بخش with خطهای فایل را در لیستی ذخیره کنید و بعداً با آن لیست کار کنید. میتوانید بخشهایی از فایل را بلافاصله پس از خواندن پردازش کنید و بخشی دیگر از پردازش را به زمان دیگری موکول کنید.
مثال زیر خطهای pi_digits.txt را در داخل بخش with در یک لیست ذخیره میکند و سپس خارج از آن، خطها را چاپ میکند:
filename ='pi_digits.txt'with open(filename) as file_object:
lines = file_object.readlines()
for line in lines:
print(line.rstrip())
در ۱ متد readlines() هر خطّ فایل را در یک لیست ذخیره میکند. سپس این لیست به lines نسبت داده میشود که میتوانیم پس از پایان بخش with هم با آن کار کنیم. در ۲ از یک حلقهی for ساده برای چاپ کردن هر خط در lines استفاده میکنیم. از آنجاییکه هر عضو lines متناظر با یک خطّ فایل است، خروجی عیناً مشابه محتویّات فایل خواهد بود.
10.4 - کار کردن با محتویّات یک فایل
پس از آنکه فایل را خوانده و در حافظه قرار دادید، میتوانید هر کاری خواستید با آن دادهها انجام دهید، بنابراین بیایید رقمهای پی را به طور مختصر بررسی کنیم. ابتدا تلاش میکنیم که رشتهای بسازیم که شامل تمام رقمهای داخل فایل باشد و هیچ فاصلهی خالیای در آن نباشد.
filename ='pi_digits.txt'with open(filename) as file_object:
lines = file_object.readlines()
pi_string =''for line in lines:
pi_string += line.rstrip()
print(pi_string)
print(len(pi_string))
مشابه کاری که در مثال قبل کردیم، فایل را باز میکنیم و هر خط از رقمها را در یک لیست ذخیره میکنیم. در ۱ متغیّر pi_string را میسازیم تا رقمهای pi را دربربگیرد. سپس حلقهای ایجاد میکنیم که هر خطّ از رقمها را به pi_string افزوده و کاراکتر خطّ جدید را از هر خط حذف میکند (۲). در ۳ رشته را چاپ میکنیم و طول آن را نمایش میدهیم:
متغیّر pi_string شامل فاصلههایی است که در سمت چپ رقمهای هر خط وجود دارد، امّا میتوانیم با بهکارگیری strip() به جای rstrip() از شرّ آنها خلاص شویم:
زمانی که پایتون فایلی متنی را میخواند تمام متن آن را به عنوان رشته در نظر میگیرد. اگر مقداری عددی را خواندهاید و میخواهید با آن محاسبات عددی انجام دهید، باید آن را به کمک تابع int() به عدد صحیح یا به کمک تابع float() به عدد اعشاری تبدیل کنید.
فایلهای بزرگ: یک میلیون رقم اعشار
تا اینجا بر روی تحلیل فایلی متنی تمرکز کردهایم که تنها سه خط را شامل میشد امّا کد این مثالها میتوانند با فایلهای بسیار بزرگتر هم به خوبی کار کنند. اگر با فایلی متنی شروع کنیم که به جای ۳۰ رقم، عدد پی را تا یک میلیون رقم اعشار داشته باشد، میتوانیم رشتهای بسازیم که تمام این رقمها را دربربگیرد. نیاز به ایجاد هیچ تغییری در برنامه نیست به جز این که نام فایل ورودی را عوض کنیم. ما همچنین فقط ۵۰ رقم اوّل را چاپ میکنیم تا نیاز نباشد یک میلیون رقم را در ترمینال ببینیم:
پایتون هیچ نوع محدودیّت ذاتی برای حجم دادهای که میخواهید با آن کار کنید، ندارد؛ میتوانید با هر چهقدر داده که حافظهی سیستم شما میتواند در خود جا دهد کار کنید.
نکته
برای اجرا کردن این برنامه (و بسیاری از مثالهایی که از این پس میآیند) باید منابع موجود در https://nostarch.com/pythoncrashcourse2e را دریافت کنید.
آیا تاریخ تولّد شما در عدد پی وجود دارد؟
من همیشه کنجکاو بودهام که بدانم آیا تاریخ تولّدم در جایی از رقمهای عدد پی وجود دارد یا نه. بیایید از برنامهای که الان نوشتیم استفاده کنیم تا ببینیم تاریخ تولّد فرد در یک میلیون رقم اعشار اوّل عدد پی وجود دارد یا نه. این کار را میتوانیم با بیان روز تولّد هر کس به صورت یک رشته و سپس جستوجوی آن رشته در pi_string انجام دهیم:
--snip--for line in lines:
pi_string += line.strip()
birthday = input("Enter your birthday, in the form mmddyy: ")
if birthday in pi_string:
print("Your birthday appears in the first million digits of pi!")
else:
print("Your birthday does not appear in the first million digits of pi.")
در ۱ از کاربر میخواهیم که تاریخ تولّد خود را وارد کند و در ۲ بررسی میکنیم که آیا آن رشته در pi_string وجود دارد یا نه. آن را امتحان میکنیم:
Enter your birthdate, in the form mmddyy: 120372
Your birthday appears in the first million digits of pi!
روز تولّد من در رقمهای اعشار پی پیدا شد! پس از این که فایلی را خواندید میتوانید محتویّات آن را به هر شکلی که تصوّر میکنیم تحلیل و بررسی کنید.
10.5 - خودآزمایی
۱۰-۱- یادگیریِ پایتون: فایلی خالی در ویرایشگر متنی خود باز کنید و در چند خط آنچه تا اینجا در مورد پایتون آموختهاید خلاصه کنید. هر خط را به این شکل آغاز کنید: In Python you can… فایل را با نام learning_python.txt در همان پوشهای ذخیره کنید که تمرینهای این فصل را در آن قرار میدهید. برنامهای بنویسید که فایل را بخواند و آنچه را نوشتهاید سه بار چاپ کند. محتوّیات را یک بار با خواندن کامل فایل، یک بار با حلقه زدن بر شیء فایل و یک بار با ذخیره کردن خطهای آن در یک لیست و سپس کار کردن با آن بیرون از بخش with، چاپ کنید.
۱۰-۲- یادگیری C: میتوانید به کمک متد replace() هر کلمهای را در یک رشته با کلمهای دیگر جایگزین کنید. اینجا مثالی سریع دربارهی جایگزینی 'dog' با 'cat' در یک جمله میبینید:
>>> message ="I really like dogs.">>> message.replace('dog', 'cat')
'I really like cats.'
فایلی که هماکنون ساختهاید، learning_python.txt، خط به خط بخوانید و کلمهی Python را با نام زبان دیگری، مثلاً C، جایگزین کنید. هر خطِ تغییریافته را روی صفحه چاپ کنید.
10.6 - نوشتن در یک فایل
یکی از سادهترین راهها برای ذخیرهسازی دادهها نوشتن آنها در یک فایل است. زمانی که متنی را در فایلی مینویسید، خروجی بعد از بستن ترمینالی که آن را نشان میدهد، همچنان در دسترس خواهد بود. میتوانید خروجی را پس از پایان اجرای یک برنامه بررسی کنید یا آن را در اختیار دیگران قرار دهید. همچنین میتوانید برنامههایی بنویسید که متن را دوباره به حافظه منتقل کرده و بعداً با آنها کار کند.
نوشتن در یک فایل خالی
برای نوشتن در یک فایل باید open() را با نشانوند دومی فراخوانی کنید که به پایتون میگوید میخواهید در آن فایل چیزی بنویسید. برای این که ببینیم این کار چگونه انجام میشود، پیام سادهای مینویسیم و به جای نمایش آن بر روی صفحه، آن را در یک فایل ذخیره میکنیم:
filename ='programming.txt'with open(filename, 'w') as file_object:
file_object.write("I love programming.")
در این مثال فراخوانی open() در ۱ با دو نشانوند انجام میشود. اوّلین نشانوند همچنان نام فایلی9594
است که میخواهیم باز کنیم. دومین نشانوند 'w'، به پایتون میگوید که میخواهیم فایل را در حالت نوشتن باز کنید. شما میتوانید فایلی را در حالت خواندن ('r')، حالت نوشتن ('w')، حالت الحاق ('a') و یا حالتی که به شما اجازهی خواندن و نوشتن فایل میدهد ('r+') باز کنید. اگر نشانوند مربوط به حالت خواندن را حذف کنید، پایتون به طور پیشفرض فایل را در حالت فقط-خواندنی باز میکند.
اگر فایلی که میخواهید در آن چیزی بنویسید وجود نداشته باشد، تابع open() آن را به طور خودکار برای شما میسازد. با این حال هنگام باز کردن فایل در حالت ('w') دقّت کنید؛ چون اگر قبلاً فایلی به این نام وجود داشته باشد، پایتون پیش از بازگرداندن شیء فایل، تمام محتویّات آن را پاک میکند.
در ۲ از متد write() بر روی شیء فایل استفاده میکنیم تا رشتهای در آن بنویسیم. این برنامه هیچ خروجیای ندارد امّا اگر فایل programming.txt را باز کنید در آن یک خط میبینید:
این فایل مانند هر فایل دیگری در رایانهی شما رفتار میکند. میتوانید آن را باز کنید، متن جدیدی در آن بنویسید، از آن چیزی کپی کنید، به آن چیزی اضافه کنید و … .
نکته
پایتون تنها میتواند در یک فایل متنی رشتهها را بنویسد. اگر میخواهید دادههای عددی را در یک فایل متنی ذخیره کنید باید ابتدا آن را به کمک تابع str() به شکل متنی دربیاورید.
نوشتن چند خط
تابع write() هیچ خطّ جدیدی به متن شما اضافه نمیکند. بنابراین اگر بدون افزودن کاراکتر خطّ اضافه بیشتر از یک خط بنویسید، ممکن است فایل شما آنجور که انتظار دارید به نظر نیاید:
filename ='programming.txt'with open(filename, 'w') as file_object:
file_object.write("I love programming.")
file_object.write("I love creating new games.")
افزودن خطهای جدید هنگام فراخوانی write() هر رشته را در خط خود جای میدهد:
filename ='programming.txt'with open(filename, 'w') as file_object:
file_object.write("I love programming.\n")
file_object.write("I love creating new games.\n")
شما میتوانید از فاصله، کاراکترِ تب یا خطهای خالی برای شکل دادن به خروجی خود استفاده کنید همانگونه که در خروجیهای خود در ترمینال انجام میدادید.
اضافه کردن به یک فایل
اگر میخواهید مطلبی به یک فایل اضافه کنید، به جای رونویسی بر مطالب قبلی میتوانید فایل را در حالت الحاق باز کنید. زمانی که فایل را در این حالت باز میکنید، پایتون پیش از بازگرداندنِ شیء، محتویّات آن را پاک نمیکند. هر خطّی که در فایل بنویسید به انتهای آن افزوده خواهد شد. اگر فایل وجود نداشته باشد، پایتون یک فایل خالی برای شما خواهد ساخت.
حال write_message.py را با اضافه کردن چند دلیل برای دوستداشتن برنامهنویسی به فایلِ موجودِ programming.txt تغییر میدهیم:
filename ='programming.txt'with open(filename, 'a') as file_object:
file_object.write("I also love finding meaning in large datasets.\n")
file_object.write("I love creating apps that can run in a browser.\n")
در ۱ از نشانوند 'a' برای باز کردن فایل استفاده میکنیم تا به جای این که محتویّات آن را رونویسی کنیم، به آن چیزی اضافه کنیم. در ۲ دو خطّ جدید مینویسیم که به programming.txt اضافه میشوند:
I love programming.
I love creating new games.
I also love finding meaning in large datasets.
I love creating apps that can run in a browser.
در نهایت به همان محتویّات اصلی فایل میرسیم که دو خطّ جدید به آن اضافه شده است.
10.7 - خودآزمایی
۱۰-۳- مهمانها: برنامهای بنویسید که از کاربران نام آنها را بپرسد. زمانی که پاسخ دادند، نام آنها در فایلی به نام guest.txt بنویسید.
۱۰-۴- کتابچهی مهمانها: یک حلقهی while بنویسید که از کاربران نامشان را بپرسد. زمانی که نام خود را وارد کردند، پیام سلامی خطاب به آنها روی صفحه چاپ کنید و در فایلی به نام guest_book.txt خطی برای ثبت کردنِ بازدیدِ آنها اضافه کنید. مطمئن شوید که در فایل هر عنوان ورودی در یک خط جدید ذخیره میشود.
۱۰-۵- نظرسنجی برنامهنویسی: یک حلقهی while بنویسید که از افراد بپرسد چرا برنامهنویسی را دوست دارند. هر بار که کسی دلیل وارد کرد، آن را به فایلی که تمام پاسخها را نگه میدارد، اضافه کنید.
10.8 - استثناها
پایتون از شیءی خاص به نام *استثنا * برای مدیریّت خطاهایی که حین اجرای برنامه رخ میدهند استفاده میکند. هرگاه پس از رخ دادن خطایی، پایتون مطمئن نباشد که چه باید بکند، یک شیء استثنا میسازد. اگر کدی برای رسیدگی کردن به استثنا بنویسید برنامه به کار خود ادامه خواهد داد. اگر استثنا را مدیریّت نکنید برنامه متوقّف شده و یک traceback نمایش داده میشود که حاوی گزارشی در مورد استثنایی است که رخ داده است.
رسیدگی به استثناها با بلوکهای try-except انجام میپذیرد. بلوک try-except از پایتون میخواهد که کاری انجام دهد و امّا در عین حال مشخّص میکند که در صورت رخ دادن استثنا حین انجام آن کار چه کاری بکند. زمانی که از بلوکهای try-except استفاده میکنید برنامههای شما حتی در صورت به اشتباه افتادن به کار خود ادامه میدهند. کاربران به جای traceback که ممکن است خواندن آن برای آنها گیجکننده باشد، پیامهایی مناسب میبینند که شما خود نوشتهاید.
رسیدگی به استثنای ZeroDivisionError
به خطایی ساده که موجب میشود اعلام استثنا کند میپردازیم. احتمالاً میدانید که تقسیم کردن عددی بر صفر ناممکن است، امّا بگذارید از پایتون بخواهیم این کار را انجام دهد:
Traceback (most recent call last):
File "division_calculator.py", line 1, in <module>
print(5/0)
ZeroDivisionError: division by zero
خطایی که در traceback نشان داده شده، ZeroDivisionError، یک شیء استثنا است. پایتون چنین شیءای را در واکنش به وضعیّتی میسازد که در آن نمیتواند کاری را که از آن خواستهایم انجام دهد. زمانی که چنین چیزی رخ میدهد، پایتون برنامه را متوقّف کرده و به ما میگوید چه نوع استثنایی رخ داده است. ما میتوانیم از این اطّلاعات برای تغییر دادن و اصلاح برنامهی خود استفاده کنیم. به پایتون میگوییم زمانی که چنین استثنایی رخ داد چه کاری انجام بده؛ با این کار اگر چنین موقعیّتی دوباره پیش بیاید ما آمادگی داریم.
استفاده از بلوکهای try-except
زمانی که فکر میکنید ممکن است خطایی رخ دهد، میتوانید یک بلوک try-except بنویسید تا به استثنایی که احتمالاً رخ میدهد رسیدگی کنید. از پایتون میخواهید که کدی را اجرا کند و به آن میگویید در صورتی که اجرای کد به وقوع نوع خاصّی از استثنا انجامید، چه کاری انجام دهد.
اینجا نشان میدهیم که بلوک try-except برای رسیدگی به استثنای ZeroDivisionError چه شکلی است:
try:
print(5/0)
exceptZeroDivisionError:
print("You can't divide by zero!")
خطّی را که موجب بروز خطا میشود، print(5/0)، در بلوک try قرار میدهیم. اگر کد داخل try کار کند، پایتون از روی بلوک except میپرد. اگر کد داخل بلوک try منجر به خطا شود، پایتون به دنبال بلوک exceptای میگردد که استثنای آن با استثنای رخداده منطبق باشد و کد داخل آن را اجرا میکند.
در این مثال کد داخل try خطای ZeroDivisionError را تولید میکند بنابراین پایتون به دنبال بلوک exceptای میگردد که به او بگوید چه باید بکند. پایتون سپس کد داخل آن بلوک را اجرا میکند و کاربر به جای traceback یک پیام خطای دوستانه میبیند:
اگر پس از بلوک try-except کد دیگری نوشته شده باشد، اجرای برنامه ادامه مییابد زیرا به پایتون گفتهایم که چگونه خطا را مدیریّت کند.
10.9 - بهکارگیری استثناها برای پیشگیری از از کار افتادن برنامه
مدیریّت خطاها به ویژه زمانی مهم است که برنامه پس از رخ دادن خطا کارِ زیادی برای انجام دادن داشته باشد. این وضع اغلب در برنامههایی رخ میدهد که از کاربر ورودی میگیرند. اگر برنامه به ورودیهای نامناسب به درستی پاسخ دهد، به جای از کار افتادن میتواند به ورودیهای درستِ بیشتری رسیدگی کند.
بیایید ماشین حساب سادهای بسازیم که تنها عمل تقسیم را انجام میدهد:
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")
whileTrue:
first_number = input("\nFirst number: ")
if first_number =='q':
break second_number = input("Second number: ")
if second_number =='q':
break answer = int(first_number) / int(second_number)
print(answer)
این برنامه از کاربر میخواهد تا first_number را وارد کند (۱) و اگر برای خروج q را فشار نداد، second_number را هم وارد کند (۲). سپس این دو عدد را بر هم تقسیم میکنیم تا answer به دست بیاید (۳). این برنامه هیچ کاری برای مدیریّت خطا انجام نمیدهد بنابراین درخواست تقسیم بر صفر موجب از کار افتادن برنامه میشود:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
Traceback (most recent call last):
File "division_calculator.py", line 9, in <module>
answer = int(first_number) / int(second_number)
ZeroDivisionError: division by zero
این که برنامه از کار افتاده بد است؛ و علاوه بر آن نشان دادنِ traceback به کاربر هم فکر خوبی نیست. زیرا کاربران غیر فنّی را گیج میکند و در صورتی که کسی به دنبال کارهای مخرّب باشد، به کمکِ آن ممکن است بیش از آن چیزی که شما بخواهید در مورد برنامهیتان اطّلاعات کسب کند. مثلاً نام برنامهی شما را میفهمد یا میبیند که کجای برنامهی شما به درستی کار نمیکند. یک مهاجم کارآزموده گاهی میتواند از این دانستهها استفاده کند تا نوعِ حمله علیهِ کد شما را تعیین کند.
بلوک else
میتوانیم با قرار دادن خطّی که ممکن است ایجاد خطا کند داخل یک بلوک try این برنامه را در برابر خطا مقاومتر کنیم. خطا در خطّی رخ میدهد که تقسیم در آن انجام میگیرد بنابراین آنجا جایی است که در بلوک try-except قرار میدهیم. این مثال همچنین شامل یک بلوک else است. هر کدی که اجرای آن به اجرای موفّق بلوک try وابسته است در بلوک else قرار میگیرد:
از پایتون میخواهیم که تلاش خود را برای به انجام رساندن عملیّات تقسیم در یک بلوک try بکند (۱). این بلوک تنها شامل کدی است که ممکن است موجب بروز خطا شود. هر کدی که به اجرای موفقیّتآمیز بلوک try وابسته باشد به بلوک else افزوده میشود. در این مورد اگر عملیّات تقسیم موفّق باشد از بلوک else برای چاپ کردن نتیجه در ۳ استفاده میکنیم.
بلوک except به پایتون میگوید که هنگام رخ دادن ZeroDivisionError چه پاسخی بدهد (۲). اگر بلوک try به دلیل خطای تقسیم بر صفر با موفقیّت انجام نشد، پیامی دوستانه چاپ میکنیم که به کاربر بگوید چگونه از این خطا پرهیز کند. اجرای برنامه ادامه مییابد و کاربر هرگز tracebackای نمیبیند:
Give me two numbers, and I'll divide them.
Enter 'q' to quit.
First number: 5
Second number: 0
You can't divide by 0!
First number: 5
Second number: 2
2.5
First number: q
بلوک try-except-else این گونه کار میکند: پایتون تلاش میکند که کد داخل بلوک try را اجرا کند. تنها کدی باید در این بلوک قرار بگیرد که ممکن است موجب بروز استثنا شود. گاهی شما کد دیگری دارید که باید تنها در صورت موفقیّتآمیز بودن اجرای بلوک try اجرا شود؛ این کد در بلوک else قرار میگیرد. بلوک except به پایتون میگوید که در صورت وقوع استثنای خاصّی در اجرای کد بلوک try، چه کاری انجام دهد.
با پیشبینی منابع احتمالی خطا میتوانید برنامههای مقاومی بنویسید که حتّی در صورت برخوردن به دادههای نامعتبر یا پیدا نشدنِ منابع، به کار خود ادامه دهند. کد شما در برابر خطاهای غیرعمدی کاربران یا حملههای مخرّب مقاومت خواهد کرد.
10.10 - رسیدگی به استثنای FileNotFoundError
یکی از مشکلات شایع هنگام کار کردن با فایلها مدیریّت فایلهای ناموجود است. فایلی که به دنبال آن هستید ممکن است در جای متفاوتی باشد، نام آن ممکن است اشتباه نوشته شده باشد یا چه بسا اصلاً وجود نداشته باشد. شما میتوانید به کمک یک بلوک try-except و با روشی سرراست به تمام این وضعیّتها رسیدگی کنید.
حال بیایید تلاش کنیم فایلی را که وجود ندارد بخوانیم. برنامهی زیر میخواهد محتویّات آلیس در سرزمین عجایب را بخواند امّا من فایل alice.txt را در پوشهی alice.py ذخیره نکردهام:
filename ='alice.txt'with open(filename, encoding='utf-8') as f:
contents = f.read()
اینجا دو تغییر وجود دارد. یکی بهکارگیری متغیّر f برای نمایش شیء فایل است که قراردادی مرسوم است. دیگری استفاده از نشانوند encoding است. این نشانوند هنگامی مورد نیاز است که کدگذاری پیشفرض سیست 97
م شما با کدگذاری فایلی که میخواهید بخوانید همخوانی ندارد.
پایتون نمیتواند فایلی را که وجود ندارد بخواند پس استثنایی ایجاد میکند:
Traceback (most recent call last):
File "alice.py", line 3, in <module>
with open(filename, encoding='utf-8') as f:
FileNotFoundError: [Errno 2] No such file or directory: 'alice.txt'
آخرین خط traceback خطای FileNotFoundError را گزارش میکند: زمانی که پایتون فایلی را که تلاش میکند بخواند، پیدا نمیکند این استثنا را تولید میکند.
در این مثال تابع open() ایجاد خطا میکند بنابراین برای رسیدگی به آن، بلوک try از خطّی شروع میشود که شامل open() است:
filename ='alice.txt'try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
exceptFileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
در این مثال کد داخل بلوک try ایجاد FileNotFoundError میکند پس پایتون به دنبال بلوک exceptای میگردد که به این خطا بخورد. سپس کد داخل آن بلوک را اجرا میکند که به جای یک traceback به صدور پیام دوستانهای منجر میشود:
این برنامه در صورت پیدا نشدن فایل کاری برای انجام دادن ندارد بنابراین رسیدگی به خطا چیز زیادی به آن اضافه نمیکند. اجازه دهید این مثال را ادامه دهیم و ببینیم مدیریّت استثناها هنگام کار کردن با بیش از یک فایل چه کمکی میتوانند بکنند.
تحلیل متن
شما میتوانید فایلهای متنی شامل کتابهای کامل را98
تحلیل کنید. بخش زیادی از ادبیّات کلاسیک در قالب فایلهای متنی ساده در دسترس هستند چون در قلمرو عمومی هستند. متنهای به کار رفته در این بخش از پروژهی گوتنبرگ گرفته شدهاند (http://gutenberg.org) پروژهی گوتنبرگ مجموعهای از کارهای ادبی را نگهداری میکند که در قلمرو عمومی در دسترساند و اگر در پروژههای برنامهنویسی خود به کار با متنهای ادبی علاقهمندید، منبع بسیار خوبی خواهد بود.
اجازه دهید متن آلیس در سرزمین عجایب را بیرون بکشیم تا تعداد کلمات متن آن را بشمریم. از متد رشتهای split() استفاده میکنیم که میتواند رشته را به لیستی از کلمات تبدیل کند. اینجا میبینیم که split() با رشتهای که شامل عنوان متن یعنی "Alice in Wonderland" است چه میکند:
>>> title ="Alice in Wonderland">>> title.split()
['Alice', 'in', 'Wonderland']
متد split() هر جا در رشته به فاصلهی خالی میرسد، آن بخش را جدا کرده و تمام تکّهها را در لیستی ذخیره میکند. نتیجه لیستی از کلماتِ رشته است، هرچند ممکن است در برخی از کلمات علائم سجاوندی هم پیدا شود. برای شمارش کلمات آلیس در سرزمین عجایب از split() بر روی کل متن استفاده میکنیم. سپس تنها اعضای لیست را میشماریم تا به عددی سرانگشتی از تعداد کلمات موجود در متن برسیم:
filename ='alice.txt'try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
exceptFileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
# Count the approximate number of words in the file. words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
فایل alice.txt را به پوشهی درست منتقل کردم بنابراین بلوک try این بار درست کار خواهد کرد. در ۱ رشتهی contents را که اکنون کل متن آلیس در سرزمین عجایب را به صورت یک رشتهی بلند در بر دارد، میگیریم و به کمک متد split() لیستی از تمام کلمات داخل کتاب درست میکنیم. زمانی که از len() برای بررسی طول آن استفاده میکنیم تقریب خوبی از تعداد کلمات رشتهی اصلی به دست میآوریم (۲). در ۳ عبارتی چاپ میکنیم که تعداد کلمات یافتشده در فایل را گزارش میکند. این کد داخل بلوک else قرار گرفته زیرا تنها در صورتی کار خواهند کرد که بلوک try با موفقیّت اجرا شود. خروجی به ما میگوید که تعداد کلمات موجود در alice.txt چندتاست:
این تعداد کمی از مقدار واقعی بیشتر است زیرا ناشر اطّلاعات بیشتری در فایل متنی بهکاررفته ارائه کرده است با این حال تقریب خوبی از تعداد کلمات آلیس در سرزمین عجایب است.
10.11 - کار کردن با چند فایل
میخواهیم کتابهای بیشتری برای تحلیل اضافه کنیم. امّا پیش از این کار اجازه دهید بدنهی این برنامه را به داخل تابعی به نام count_words() منتقل کنیم. با این کار تحلیل کردن چند کتاب آسانتر خواهد بود:
defcount_words(filename):
"""Count the approximate number of words in a file."""try:
with open(filename, encoding='utf-8') as f:
contents = f.read()
exceptFileNotFoundError:
print(f"Sorry, the file {filename} does not exist.")
else:
words = contents.split()
num_words = len(words)
print(f"The file {filename} has about {num_words} words.")
filename ='alice.txt'count_words(filename)
بیشتر کد دستنخورده است. صرفاً آن را با تورفتگی داخل بدنهی count_words() قرار دادهایم. بهروز نگهداشتنِ توضیحات هنگام تغییر دادن برنامه عادت خوبی است بنابراین توضیح را به یک docstring تبدیل کرده و کمی آن را تغییر دادهایم (۱).
حال حلقهی سادهای مینویسیم که تعداد کلمات هر متن99
ی را محاسبه کند. این کار را با ذخیره کردنِ نام فایلها در یک لیست و سپس فراخوانی count_words() برای هر فایل در لیست انجام میدهیم. سعی میکنیم تعداد کلمات آلیس در سرزمین عجایب، سیذارتا، mobی دیک و زنان کوچک را که همگی در قلمرو عمومی هستند، تحلیل کنیم. من به عمد فایل siddhartha.txt را از پوشهی برنامهی word_count.py بیرون گذاشتهام تا ببینم برنامه چگونه فقدان فایل را مدیریّت میکند:
The file alice.txt has about 29465 words.
Sorry, the file siddhartha.txt does not exist.
The file moby_dick.txt has about 215830 words.
The file little_women.txt has about 189079 words.
بهکارگیریِ بلوک try-except در این مثال دو مزیّت مهم دارد. مانع این میشویم که کاربر tracebackای ببیند و باعث میشویم برنامه به تحلیل متنهایی که میتواند پیدا کند ادامه دهد. اگر مانع خطای FileNotFoundError را که Siddhartha.txt موجب آن شده بود، نمیشدیم کاربر یک traceback کامل را مشاهده میکرد و برنامه پس از تلاش برای تحلیل سیذارتا متوقّف میشد. در نتیجه هیچگاه mobیدیک و زنان کوچک را بررسی نمیکرد.
رد کردنِ بیسروصدای یک خطا
در مثال پیشین به کاربران خود اطّلاع دادیم که یکی از فایلها موجود نیست. امّا نیازی نیست هر استثنایی را که رخ میدهد گزارش کنید. گاهی شما میخواهید که مشکلات برنامه بدون هیچ سروصدایی رد شوند درست مثل این که هیچ اتّفاقی نیفتاده است. برای رسیدن به این مقصود یک بلوک try مانند همیشه مینویسید امّا به پایتون به طور صریح میگویید که در بلوک except هیچ کاری نکند. دستوری به نام pass به پایتون میگوید که هیچ کاری انجام ندهد:
defcount_words(filename):
"""Count the approximate number of words in a file."""try:
--snip--exceptFileNotFoundError:
passelse:
--snip--filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']
for filename in filenames:
count_words(filename)
تنها تفاوت این برنامه با قبلی عبارت pass در ۱ است. حال وقتی FileNotFoundError رخ میدهد کد داخل بلوک except اجرا میشود امّا هیچ اتّفاقی نمیافتد. هیچ tracebackای و هیچ خروجیای در پاسخ به خطای رخداده تولید نمیشود. کاربران تعداد کلمات هر فایل موجود را میبینند امّا در مورد وجود نداشتنِ یک فایل هیچ علامتی نمیبینند:
The file alice.txt has about 29465 words.
The file moby_dick.txt has about 215830 words.
The file little_women.txt has about 189079 words.
دستور pass همچنین به عنوان یک جایبان هم عمل میکند. به شما یادآوری میکند که شما تصمیم گرفتهاید در جایی از برنامه هیچ کاری نکنید و این که ممکن است بعداً بخواهید در آن نقطه کاری انجام دهید. برای مثال در این برنامه ممکن است تصمیم بگیرید که نام فایلهای یافتنشده را در فایلی به نام missing_files.txt بنویسید. کاربران ما این فایل را نمیبینند ولی ما قادر هستیم که آن را بخوانیم و در مورد متنهای پیدانشده کاری بکنیم.
کدام خطا را گزارش کنیم؟
چگونه بفهمیم که کی باید خطا را به کاربر گزارش کنیم و چه زمانی باید از کنار آن به آرامی بگذریم؟ اگر کاربران بدانند که چه متنهایی قرار است تحلیل شوند، ممکن است از دیدنِ پیامی که میگوید چرا برخی از متنها تحلیل نشدهاند استقبال کنند. اگر کاربران منتظرِ دیدن برخی نتایج هستند و نمیدانند چه کتابهایی قرار است تحلیل شود، شاید نیازی نباشد که از نبودنِ برخی از متنها مطّلع شوند. دادنِ اطّلاعات غیرمنتظره به کاربران میتواند باعث کاهش کارایی برنامهی شما شود. ساختارهای مدیریّت خطای پایتون به شما امکان میدهد که در زمان بروز خطا اطّلاعاتی را که در اختیار کاربر قرار میدهید با جزئیات بسیاری کنترل کنید؛ این که چهقدر اطّلاعات با کاربر به اشتراک گذاشته شود به شما بستگی دارد.
برنامهای که به خوبی نوشته و به درستی آزموده شده خیلی در معرضِ خطاهای داخلی مانند خطاهای نحوی یا منطقی نیست. امّا هر بار برنامهی شما به چیزی خارج از خود وابسته باشد، مانند ورودیِ کاربر، وجودِ یک فایل یا ارتباط شبکه، امکان رخ دادنِ یک استثنا وجود دارد. کمی تجربه به شما کمک میکند که بدانید کی از بلوکهای مدیریّت استثنا استفاده کنید و به کاربر در مورد خطاهایی که رخ میدهد چهقدر اطّلاعات بدهید.
10.12 - خودآزمایی
۱۰-۶- جمع کردن: یکی از مشکلات رایج هنگام دریافت مقادیر عددی زمانی رخ میدهد که افراد به جای عدد متن وارد میکنند. وقتی تلاش میکنیم که این ورودی را با int به عدد تبدیل کنیم خطای ValueError دریافت میکنید. برنامهای بنویسید که از کاربر دو عدد بگیرد. آنها را جمع و نتیجه را چاپ کند. اگر هر کدام از ورودیها عدد نبودند خطای ValueError را بگیرید و پیامی دوستانه چاپ کنید. برنامهی خود را با وارد کردن دو عدد و سپس متنی به جای یکی از اعداد بیازمایید.
۱۰-۷- ماشین جمع کردن: کد تمرین ۱۰-۶ را در یک حلقهی while قرار دهید تا حتّی اگر کاربر یک بار به اشتباه به جای عدد متن وارد کرد، همچنان بتواند عددهای بعدی را وارد کند.
۱۰-۸- سگها و گربهها: دو فایل به نام cats.txt و dogs.txt بسازید. در اوّلی دستِ کم نام سه گربه و در دیگری نام سه سگ را ذخیره کنید. برنامهای بنویسید که سعی کند این فایلها را بخواند و محتویّات آنها را روی صفحه چاپ کند. کد خود را در یک بلوک try-except قرار دهید تا خطای FileNotFoundError را بگیرد و در صورت فقدان فایلی پیامی چاپ کند. یکی از فایلها را به مکان دیگری در سیستم خود منتقل کنید و مطمئن شوید که بلوک except به درستی اجرا میشود.
۱۰-۹- سگها و گربههای بیصدا: بلوک except را در تمرین ۱۰-۸ جوری تغییر دهید که در صورت یافت نشدنِ بدون سر و صدا آن را رد کند.
۱۰-۱۰- کلمات مشترک: پروژهی گوتنبرگ (https://gutenberg.org) را ببینید و چند متن پیدا کنید که دوست دارید آنها را تحلیل کنید. فایلهای متنی آنها را دانلود کنید یا متن خام را از مرورگر خود کپی و در یک فایل متنی در رایانهی خود ذخیره کنید.
میتوانید از متد count() استفاده کنید تا بفهمید یک کلمه یا عبارت چند بار در یک رشته تکرار شده است. برای مثال کد زیر تعداد دفعات پدیدار شدن 'row' را در یک رشته میشمارد:
>>> line ="Row, row, row your boat">>> line.count('row')
2>>> line.lower().count('row')
3
توجّه کنید که تبدیل تمام رشته به حروف کوچک به کمک lower() تمام موارد وقوع یک کلمه را صرف نظر از شکل و بزرگ و کوچکی حروف آن پیدا میکند.
برنامهای بنویسید که فایلهایی را که در پروژهی گوتنبرگ پیدا کردهاید بخواند و تعیین کند که کلمهی 'the' چند بار در هر کدام تکرار شده است. این عدد تقریبی خواهند بود زیرا کلمههایی مانند 'then' و 'there' هم شمرده خواهند شد. حال شمردن 'the ' را امتحان کنید، با افزودنِ یک فاصله در انتهای رشته، و ببینید که تعداد نسبت به قبل چهقدر کمتر میشود.
10.13 - ذخیرهسازی دادهها
بسیاری از برنامههای شما از کاربران میخواهند که انواع خاصّی اطّلاعات را وارد کنند. ممکن است به کاربران امکان دهید که تنظیمات پیشفرض خود را در یک بازی ذخیره یا دادههایی را برای بصریسازی ارائه کنند. تمرکزِ برنامهی شما بر هر چیزی باشد، شما اطّلاعاتی را که کاربر ارائه میکند در ساختمانهای دادهای مانند لیستها و دیکشنریها ذخیره میکنید. زمانی که کاربران برنامهای را میبندند تقریباً همیشه میخواهید که اطّلاعات وارد شده را ذخیره کنید. یک راه ساده برای این کار ذخیره کردن داده به کمک ماژول json است.
ماژول json به شما امکان میدهد که ساختمانهای دادهی سادهی پایتون را درون یک فایل بریزید و دفعهی بعد که برنامه اجرا شد آنها را دوباره بارگذاری کنید. همچنین میتوانید از json برای به اشتراک گذاشتن اطّلاعات بین برنامههای مختلف پایتون استفاده کنید. و حتّی بهتر از این، شکل دادهی JSON منحصر به پایتون نیست بنابراین میتوانید دادههایی را که به صورت JSON ذخیره کردهاید با افرادی که با بسیاری دیگری از زبانهای برنامهنویسی کار میکنند به اشتراک بگذارید.
نکته
JSON در ابتدا برای جاوااسکریپت توسعه داده شد. با این حال مدتهاست که به عنوان شکل فایلی مشترک بین زبانهای بسیاری از جمله پایتون تبدیل شده است.
استفاده از json.dump() و json.load()
بیایید برنامهای برای ذخیره کردن مجموعهای عدد و برنامهای دیگر برای خواندن دوبارهی آن عدد بنویسیم. برنامهی اوّل از json.dump() برای ذخیره کردن مجموعهی اعداد و برنامهی دوم از json.load() استفاده میکند.
تابع json.dump() دو نشانوند میگیرد: قطعهای داده و شیء فایلی که برای ذخیره کردن آن استفاده میشود. اینجا چگونگی استفاده از json.dump() را برای ذخیره کردن یک لیست اعداد میبینید:
ابتدا ماژول json را وارد میکنیم و لیستی از اعداد میسازیم. در ۱ نام فایلی را که در آن لیست اعداد ذخیره میشود انتخاب میکنیم. برای نشان دادن این که دادهها در فایل به شکل JSON ذخیره میشوند، مرسوم است که از پسوند .json برای آن استفاده کنیم. سپس در ۲ فایل را در حالت نوشتن باز میکنیم تا به json اجازه بدهیم دادهها را در فایل بنویسد. در ۳ از تابع json.dump() برای ذخیره کردن لیست numbers در فایل numbers.json استفاده میکنیم.
این برنامه هیچ خروجیای ندارد امّا اجازه دهید فایل numbers.json را باز کنیم و نگاهی به آن بیندازیم. داده به همان شکلی ذخیره شده که در پایتون دیده میشود:
در ۱ مطمئن میشویم که اطّلاعات را از همان فایلی میخوانیم که در آن ذخیره کردهایم. این بار فایل را در حالت خواندن باز میکنیم چون پایتون تنها نیاز دارد که فایل را بخواند (۲). در ۳ از تابع json.load() برای بارگذاری اطّلاعات ذخیرهشده در numbers.json استفاده میکنیم و آن را به متغیّر numbers نسبت میدهیم. در نهایت لیست اعداد بازیابیشده را چاپ میکنیم و میبینیم که همان لیست ساختهشده در number_writer.py است:
این راهی ساده برای به اشتراک گذاشتن دادهها بین دو برنامه است.
10.14 - ذخیره کردن و خواندن دادههایی که کاربر تولید کرده
هنگام کار با دادههایی که کاربر تولید کرده، ذخیره کردن آنها با JSON سودمند است، زیرا اگر اطّلاعات او را جایی به شکلی ذخیره نکنید، با توقّف اجرای برنامه آنها را از دست خواهید داد. به مثالی نگاه میکنیم که در آن در اوّلین اجرای برنامه از کاربر نام او را میپرسیم و سپس زمانی که برنامه را دوباره اجرا کرد، نامش را به خاطر میآوریم.
import json
username = input("What is your name? ")
filename ='username.json'with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
در ۱ یک نام کاربری را برای ذخیره کردن میپرسیم. سپس از json.dump() استفاده میکنیم و نام کاربری و یک شیء فایل را به آن میدهیم تا نام کاربری را در یک فایل ذخیره کند (۲). سپس پیامی چاپ میکنیم که ذخیره شدنِ اطّلاعات را به کاربران اطّلاع میدهد (۳):
در ۱ از json.load() برای خواندنِ اطّلاعات ذخیره شده در username.json استفاده و آن را به متغیّر username نسبت میدهیم. اکنون نامهای کاربری را بازیابی کردهایم و میتوانیم به آنها خوشآمد بگوییم (۲):
ما به ترکیب این دو برنامه در یک فایل نیاز داریم. زمانی که کسی remember_me.py را اجرا میکند، میخواهیم در صورتِ امکان، نامِ کاربریِ او را از حافظه بازیابی کنیم؛ بنابراین با یک بلوک try شروع میکنیم که سعی میکند نام کاربری را بازیابی کند. اگر فایل username.json وجود نداشته باشد، در بلوک except نام کاربری را میپرسیم و برای دفعات بعد در username.json ذخیره میکنیم:
import json
# Load the username, if it has been stored previously.# Otherwise, prompt for the username and store it.filename ='username.json'try:
with open(filename) as f:
username = json.load(f)
exceptFileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
هیچ کد جدیدی اینجا وجود ندارد؛ فقط بلوکهای کد دو مثال قبلی را در یک فایل ترکیب کردهایم. در ۱ تلاش میکنیم که فایل username.json را باز کنیم. اگر این فایل وجود داشته باشد، نام کاربری را از آن میخوانیم (۲) و پیام خوشآمدی خطاب به کاربر در بلوک else چاپ میکنیم. اگر این اوّلین باری باشد که کاربر برنامه را اجرا میکند، پس username.json وجود ندارد و خطای FileNotFoundError رخ خواهد داد (۳). پایتون به بلوک except میرود و در آنجا از کاربر نام کاربریاش را میپرسد (۴). سپس از json.dump() برای ذخیره کردن نام کاربری استفاده میکنیم و پیامی چاپ میکنیم (۵).
هر کدام از بلوکها که اجرا شوند، نتیجه یک نام کاربری و یک پیام مناسب خواهد بود. اگر اوّلین اجرای برنامه باشد، خروجی اینچنین خواهد بود:
این خروجیای است که اگر برنامه قبلاً یک بار اجرا شده باشد خواهید دید.
10.15 - بازآرایی
اغلب شما به جایی میرسید که کدتان کار میکند امّا101
تشخیص میدهید که با شکستن کد به مجموعهای از تابعها با وظیفهی ویژه، میتوانید آن را بهبود دهید. این فرآیند را بازآرایی مینامند. بازآرایی کد شما را تمیزتر و خواندن و توسعهی آن را سادهتر میکند.
ما میتوانیم remember_me.py را با انتقال منطق آن به یک یا چند تابع بازآرایی کنیم. تمرکز remember_me.py بر تبریک گفتن به کاربر است بنابراین تمام کد موجود را به تابعی به نام greet_user() منتقل میکنیم:
import json
defgreet_user():
"""Greet the user by name.""" filename ='username.json'try:
with open(filename) as f:
username = json.load(f)
exceptFileNotFoundError:
username = input("What is your name? ")
with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
else:
print(f"Welcome back, {username}!")
greet_user()
چون الان از یک تابع استفاده میکنیم، توضیحات را با یک docstring جایگزین میکنیم که توضیح میدهد برنامه در حال حاضر چگونه کار میکند (۱). این فایل کمی واضحتر است امّا تابع greet_user() کارهای دیگری جز سلام کردن به کاربر هم انجام میدهد—اگر نام کاربری قبلاً وجود داشته باشد آن را بازیابی میکند و اگر نه، برای وارد کردن یک نام کاربری جدید اقدام میکند.
بیایید greet_user() را بازآرایی کنیم تا این همه کار مختلف را انجام ندهد. کار را با انتقال کدی که نام کاربری ذخیرهشده را بازیابی میکند شروع میکنیم و آن را به یک تابع جدا منتقل میکنیم:
import json
defget_stored_username():
"""Get stored username if available.""" filename ='username.json'try:
with open(filename) as f:
username = json.load(f)
exceptFileNotFoundError:
returnNoneelse:
return username
defgreet_user():
"""Greet the user by name.""" username = get_stored_username()
if username:
print(f"Welcome back, {username}!")
else:
username = input("What is your name? ")
filename ='username.json'with open(filename, 'w') as f:
json.dump(username, f)
print(f"We'll remember you when you come back, {username}!")
greet_user()
تابعِ جدیدِ get_stored_username() هدف واضحی دارد چنانچه در docstring در ۱ هم ذکر شده است. این تابع نام کاربری ذخیرهشده را بازیابی میکند و در صورت پیدا شدن آن را برمیگرداند. اگر فایل username.json وجود نداشته باشد این تابع None برمیگرداند (۲). این تمرین خوبی است: تابع یا باید مقداری را که شما انتظار دارید یا در غیرِ این صورت None برگرداند. این به ما امکان میدهد که خروجی تابع را به سادگی بررسی کنیم. در ۳ اگر بازیابی نام کاربری موفّقیتآمیز بود پیامی به کاربر نمایش میدهیم و اگر نه، نام کاربری جدید را درخواست میکنیم.
باید یک بلوک دیگر کد را از greet_user() بیرون بکشیم. اگر نام کاربری وجود نداشته باشد، باید کدی که تقاضای نام کاربری جدید میکند به تابعی مخصوصِ این کار انتقال یابد:
import json
defget_stored_username():
"""Get stored username if available."""--snip--defget_new_username():
"""Prompt for a new username.""" username = input("What is your name? ")
filename ='username.json'with open(filename, 'w') as f:
json.dump(username, f)
return username
defgreet_user():
"""Greet the user by name.""" username = get_stored_username()
if username:
print(f"Welcome back, {username}!")
else:
username = get_new_username()
print(f"We'll remember you when you come back, {username}!")
greet_user()
در این نسخهی نهایی remember_me.py هر تابع یک هدف مشخّص دارد. ما greet_user() را فراخوانی میکنیم و آن تابع پیامی مناسب چاپ میکند: یا به کاربری را که قبلاً وجود داشته خوشآمد میگوید یا به کاربر جدید سلام میکند. این تابع این کار را با فراخواندن get_stored_username() انجام میدهد که تنها مسئول بازیابی نام کاربری (به شرطِ وجود) است. سرانجام greet_user() تابع get_new_username() را در صورت لزوم صدا میکند که این تابع آخر فقط مسئول گرفتن نام کاربری جدید و ذخیره کردنِ آن است. بخش بخش کردنِ کار، قسمتِ مهمّی از نوشتن کدی شفّاف است که بتوان آن را به سادگی نگهداری کرد و توسعه داد.
10.16 - خودآزمایی
۱۰-۱۱- عدد دلخواه: برنامهای بنویسید که عدد دلخواهِ کاربر را بپرسد. از json.dump() برای ذخیره کردن این عدد در یک فایل استفاده کنید. برنامهای جدا بنویسید که این مقدار را بخواند و پیامی مانند این چاپ کند: "I know your favorite number! It's ____"
۱۰-۱۲- عددِ دلخواهِ بهخاطرسپردهشده: دو برنامهی تمرین ۱۰-۱۱ را در یک فایل ترکیب کنید. اگر عدد قبلاً وجود داشت، آن را به کاربر گزارش کنید. اگر نه، از کاربر عدد دلخواهش را بپرسید و در یک فایل ذخیره کنید. برنامه را دوبار اجرا کنید تا مطمئن شوید کار میکند.
۱۰-۱۳- تاییدِ کاربر: آخرین نسخهی remember_me.py فرض میکند که یا کاربر قبلاً نام کاربری خود را وارد کرده یا اوّلین بار است که برنامه اجرا میشود. در صورتیکه کاربر فعلی آخرین کسی نباشد که از برنامه استفاده کرده، باید برنامه را تغییر دهیم.
پیش از چاپ کردن پیام خوشآمدگویی در greet_user()، از کاربر بپرسید که آیا این نام کاربری درست است یا نه. اگر نبود get_new_username() را دوباره فراخوانی کنید تا نام کاربری تصحیح شود.
10.17 - خلاصه
در این فصل کار کردن با فایلها را آموختید. شما خواندن یکبارهی یک فایل و خواندنِ خط به خط محتویّات فایل را یاد گرفتید. دیدید که چگونه میتوان در فایلی نوشت یا متنی به انتهای آن افزود. دربارهی استثناها و نحوهی مدیریّت استثناهایی که ممکن است در برنامه رخ دهد، خواندید. سرانجام آموختید که چگونه ساختمانهای دادهی پایتون را ذخیره کنید تا اطّلاعاتی که کاربران ارائه میکنند حفظ شود و نیاز نباشد که در هر اجرای برنامه یک بار آنها را از اوّل وارد کنند.
در فصل ۱۱ روشهای موثّری برای آزمودن کد خود میآموزید که کمک میکند به کد خود به عنوان کدی که درست توسعه داده شده، اعتماد کنید و همچنین هنگامی که به توسعهی برنامههایی میپردازید که قبلاً نوشتهاید شما را در شناسایی ایراد آنها یاری میکند.
11 - آزمودن کد
11.1 - آزمودن یک تابع
میتوانید برای تابع یا کلاسی که نوشتهاید کد آزمون هم بنویسید. آزمودنِ کد ثابت میکند که کد شما در پاسخ به تمام ورودیهایی که برای دریافت آن طرّاحی شده، همانگونه که باید، کار میکند. با طرّاحی آزمونها میتوانید مطمئن باشید که حتی اگر افراد مختلفی هم از برنامهی شما استفاده کنند، به درستی کار خواهد کرد. همچنین میتوانید هر کدِ جدیدی را، همزمان با افزوده شدن، بیازمایید تا مطمئن باشید که تغییراتِ اعمالی، رفتارِ فعلی برنامه را تحتالشعاع قرار نمیدهد. هر برنامهنویسی اشتباه می کند بنابراین باید کد خود را مرتّباً مورد آزمون قرار دهد تا پیش از برخوردن کاربران به مشکلی، آن را پیدا و رفع کند.
در این فصل میآموزید که چگونه با ابزارهای موجود در ماژول unittest پایتون کد خود را بیازمایید. یاد میگیرید که یک مورد آزمایشی بسازید که در آن مجموعهای از ورودیها به تولید خروجیهایی که میخواهید منجر میشوند. خواهید دید که قبول یا رد شدن در یک آزمون به چه شکل است و همچنین رد شدن در یک آزمون چگونه به بهبودِ کد شما کمک میکند. آزمودنِ تابعها و کلاسها را میآموزید و به تدریج درخواهید یافت که برای هر پروژه چه تعداد آزمون باید بنویسید.
برای آموختنِ چگونگیِ آزمودنِ کد، به کدی برای آزمایش نیاز داریم. اینجا تابعی ساده وجود دارد که نام و نام خانوادگی را میگیرد و نام کامل را به شکلی منظّم برمیگرداند:
defget_formatted_name(first, last):
"""Generate a neatly formatted full name.""" full_name =f"{first}{last}"return full_name.title()
تابع get_formatted_name() بین نام و نام خانوادگی یک فاصله قرار میدهد، آنها را در قالبِ یک نام کامل ترکیب میکند و پس از بزرگ کردن حرفهای اوّل هر کدام، نتیجه را برمیگرداند. برای بررسی این که get_formatted_name() کار میکند یا نه برنامهای مینویسیم که از این تابع استفاده کند. برنامهی names.py به کاربران امکان ورود نام و نام خانوادگی میدهد و سپس نام کامل را به شکلی مرتّب به آنها نشان میدهد:
from name_function import get_formatted_name
print("Enter 'q' at any time to quit.")
whileTrue:
first = input("\nPlease give me a first name: ")
if first =='q':
break last = input("Please give me a last name: ")
if last =='q':
break formatted_name = get_formatted_name(first, last)
print(f"\tNeatly formatted name: {formatted_name}.")
این برنامه تابع get_formatted_name() را از name_function.py وارد میکند. کاربران میتوانند دنبالهای از نامها و نامهای خانوادگی را وارد کنند و نام کامل تولیدشده را در خروجی ببینند:
Enter 'q' at any time to quit.
Please give me a first name: janis
Please give me a last name: joplin
Neatly formatted name: Janis Joplin.
Please give me a first name: bob
Please give me a last name: dylan
Neatly formatted name: Bob Dylan.
Please give me a first name: q
میبینیم که نامهای تولیدشده درستاند. حال فرض کنید که میخواهیم تابع get_formatted_name() را طوری تغییر دهیم که بتواند نامهای میانی را هم مدیریّت کند. همچنانکه این کار را انجام میدهیم میخواهیم مطمئن باشیم که نحوهی عملکرد تابع هنگام کار کردن با نامهایی که تنها از نام و نام خانوادگی تشکیل شده، به هم نمیخورد. میتوانیم هر بار که get_formatted_name() را تغییر دادیم، names.py را اجرا کنیم و با وارد کردن نامی مانند Janis Joplin آن را آزمایش کنیم، امّا این کار به تدریج تکراری میشود. خوشبختانه پایتون راهی موثّر برای خودکارسازی فرآیندِ آزمایشِ خروجیِ یک تابع ارائه میکند. اگر آزمودن get_formatted_name() را خودکار کنیم، میتوانیم همیشه مطمئن باشیم که تابع ما با انواع نامهایی که برای آنها آزمون نوشتهایم کار میکند.
آزمون 103102
های واحد و موارد آزمایشی
ماژول unittest در کتابخانهی استاندارد پایتون ابزارهایی برای آزم}103
یش کد در اختیار شما قرار میدهد. آزمون واحد درستیِ یک جنبه از رفتار یک تابع را میسنجد. مورد آزمایشی مجموعهای از آزمونهای واحد است که در کنار هم ثابت میکنند تابع در بازهی کاملی از وضعیّتهایی که باید مدیرّیت کند، آنگونه که انتظار میرود کار میکند. یک مورد آزمایشی خوب تمام انواع ورودیهای ممکن یک تابع را در نظر میگیرد و آزمونهایی را برای نمایش هر کدام از آنها دربرمیگیرد. مورد آزمایشی با پوشش کامل گسترهی کاملی از آزمونهای واحد را شامل میشود که تمام راههای ممکن استفاده از یک تابع را پوشش میدهد. دستیافتن به پوشش کامل در یک پروژه بزرگ ممکن است ترسناک باشد. اغلب کافی است که آزمونهایی برای رفتارهای حیاتی کد بنویسیم و تنها زمانی به سراغ پوشش کامل برویم که پروژه برای کاربری بسیار گسترده آماده میشود.
11.2 - یک آزمون موفّق
کمی زمان میبرد که به روش برپایی یک مورد آزمایشی عادت کنید امّا به محض راه انداختنِ آن، افزودن آزمونهای واحد بیشتر برای تابعها سرراست خواهد بود. به منظور نوشتن یک مورد آزمایشی برای تابعی، ماژول unittest و تابعِ مورد آزمایش را وارد کنید. سپس کلاسی بسازید که از unittest.TestCase ارث میبرد و تعدادی متد بنویسید که جنبههای مختلف رفتار آن تابع را میآزماید.
این یک مورد آزمایشی با یک متد است که بررسی میکند آیا تابع get_formatted_name() با دریافت یک نام و نام خانوادگی به درستی کار میکند یا نه:
import unittest
from name_function import get_formatted_name
classNamesTestCase(unittest.TestCase):
"""Tests for 'name_function.py'."""deftest_first_last_name(self):
"""Do names like 'Janis Joplin' work?""" formatted_name = get_formatted_name('janis', 'joplin')
self.assertEqual(formatted_name, 'Janis Joplin')
if __name__ =='__main__':
unittest.main()
ابتدا unittest و تابعی را که میخواهیم آزمایش کنیم، get_formatted_name() وارد میکنیم. کلاسی به نام NamesTestCase میسازیم که تعدادی آزمایش واحد را برای get_formatted_name() شامل میشود. شما میتوانید هر نامی برای این کلاس انتخاب کنید امّا بهتر است نامی بگذارید که به تابع تحت آزمایش مربوط باشد و از واژهی Test در آن استفاده کنید. کلاس باید از کلاس unittest.TestCase ارث ببرد تا پایتون بداند چگونه آزمونهایی را که نوشتهاید اجرا کند.
NamesTestCase متدی دارد که یک جنبه از عملکردِ get_formatted_name() را میآزماید. آن را test_first_last_name() مینامیم زیرا در آن تنها نامهایی را که از نام و نام خانوادگی تشکیل شده باشند، از منظر درستیِ شکل خروجی بررسی میکنیم. هر متدی که با test_ شروع شده باشد، هنگام اجرای test_name_function.py به طور خودکار اجرا خواهد شد. داخل این متد آزمون، تابعی را که قرار است آزمایش شود صدا میکنیم. در این مثال get_formatted_name() را با نشاندهندههای 'janis' و 'joplin' صدا میکنیم و نتیجهی آن را به formatted_name نسبت میدهیم.
از یکی از سودمندترین ویژگیهای unittest بهره میگیریم: متد اثبات. متدهای اثبات بررسی میکنند که آیا نتیجهای که گرفتید یا آنچه انتظار دارید یکی است یا نه. در این مورد چون میدانیم که get_formatted_name() قرار است نام کاملی با فاصله و شکلِ مناسبِ حرفها برگرداند، انتظار داریم که مقدار formatted_name برابر با Janis Joplin باشد. برای آزمودن درستیِ این مورد از متد assertEqual() در unittest استفاده کرده و formatted_name و 'Janis Joplin' را به آن میدهیم. خطِ
میگوید: «مقدار formatted_name را به رشتهی 'Janis Joplin' مقایسه کن. اگر مطابقِ انتظار، برابر بودند، همه چیز درست است. وگرنه به من اطّلاع بده.»
این فایل را به طور مستقیم اجرا میکنیم امّا لازم ب106
ه ذکر است که بسیاری از ابزارها یا چهارچوبهای آزمون، فایلهای آزمونِ شما را پیش از اجرا وارد میکنند. مفسّر همزمان با وارد شدنِ چنین فایلی، آن را اجرا میکند. بلوک if به متغّیر ویژهای، __name__ نگاه میکند. این متغّیر هنگام اجرای برنامه تعیین میشود. اگر این فایل به عنوان برنامهی اصلی اجرا شود، مقدار __name__ برابر با '__main__' قرار داده میشود. در این مورد ما unittest.main() را صدا میکنیم که مورد آزمایشی را اجرا میکند. وقتی ابزارِ آزمون این فایل را وارد میکند مقدار __name__ برابر با '__main__' نخواهد بود و این بلوک اجرا نمیشود.
وقتی test_name_function.py را اجرا میکنیم به این خروجی میرسیم:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
نقطه در اوّلین خط خروجی به ما میگوید که آزمون موفّق بوده است. خط بعدی میگوید که پایتون یک آزمون را اجرا کرده و این کار کمتر از یک 0.001 ثانیه طول کشیده است. OK پایانی به ما میگوید که تمام آزمونهای واحد در این مورد آزمایشی موفّق بودهاند.
خروجی نشان میدهد که تابع get_formatted_name() همیشه برای نامهایی که از نام و نام خانوادگی تشکیل شدهاند کار میکند؛ مگر این که تابع را عوض کنیم. اگر get_formatted_name() را تغییر دهیم میتوانیم آزمون را دوباره اجرا کنیم. اگر آزمون موفقیّتآمیز بود میفهمیم که تابع همچنان برای نامهایی مانند Janis Joplin کار میکند.
11.3 - آزمون ناموفّق
یک آزمون ناموفّق چه شکلی است؟ بیایید get_formatted_name() را تغییر دهیم تا بتواند نامهای میانی را هم مدیرّیت کند، امّا این کار طوری انجام دهیم که تابع در صورت وارد کردن نام و نام خانوادگی به تنهایی، مانند Janis Joplin، از کار بیفتد.
این نسخهی جدید get_formatted_name() است که نام میانی را هم به عنوان نشاندهنده میپذیرد:
defget_formatted_name(first, middle, last):
"""Generate a neatly formatted full name.""" full_name =f"{first}{middle}{last}"return full_name.title()
این نسخه برای افرادی که نام میانی دارند کار میکند امّا هنگام آزمودن آن متوجّه میشویم که کارکرد تابع با وارد کردنِ نام افرادی که فقط نام و نام خانوادگی دارند خراب میشود. این بار اجرا کردن test_name_function.py این خروجی را میدهد:
E
===================================================================
ERROR: test_first_last_name (__main__.NamesTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_name_function.py", line 8, in test_first_last_name
formatted_name = get_formatted_name('janis', 'joplin')
TypeError: get_formatted_name() missing 1 required positional argument: 'last'
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
اینجا اطّلاعات زیادی وجود دارد زیرا هنگام رد شدن یک آزمون چیزهایی زیادی هست که باید بدانیم. اولّین مورد در خروجی، یک E تنهاست که نشان میدهد یک آزمون واحد در این مورد آزمایشی به خطا خورده است. پس از آن میبینیم که test_first_last_name() در NamesTestCase موجب خطا شده است. زمانی که آزمایش شما شامل چندین آزمون واحد است، دانستن این که کدام آزمون شکست خورده حیاتی است. یک traceback استاندارد میبینیم که میگوید فراخوانی تابع get_formatted_name('janis', 'joplin') دیگر کار نمیکند زیرا یک نشانوندِ مکاندارِ الزامی را ندارد. در نهایت میبینیم که یک آزمون واحد اجرا شده است. در نهایت پیامی مبنی بر شکست خوردن کلّی مورد آزمایشی میبینیم و این که هنگام اجرای آن یک خطا رخ داده است. این اطّلاعات در پایان خروجی پدیدار میشود بنابراین آن را بلافاصله میبینید بدون این که نیاز داشته باشید کل خروجی طولانی را بپیمایید تا بفهمید چند آزمون شکست خورده است.
پاسخ دادن به یک آزمون ناموفّق
اگر آزمونی شکست بخورد چه میکنید؟ فرض کنید که شما در حال بررسی شرایط درستاید: آزمون موفّق به این معنی است که تابع به درستی رفتار میکند و آزمون شکستخورده به معنای وجود خطا در کد جدیدی است که نوشتهاید. پس آزمونی را که موفّقیّتآمیز نیست، تغییر ندهید. به جای آن، کدی را که موجب رد شدن در آزمون شده اصلاح کنید. تغییراتی را که به تازگی در تابع ایجاد کردهاید بررسی کنید و دریابید که چگونه این تغییرات، رفتار مطلوب را دچار مشکل کرده است.
در این مورد، get_formatted_name() در ابتدا تنها دو پارامتر داشت: نام و نام خانوادگی. اکنون نام، نام میانی و نام خانوادگی را نیاز دارد. افزودن پارامترِ اجباریِ نامِ میانی، رفتارِ مطلوب get_formatted_name() را مختل کرده است. اینجا بهترین گزینه اختیاری کردن نام میانی است. به محض انجام این کار آزمون ما برای نامهایی مانند Janis Joplin هم موفقیّتآمیز خواهد بود و میتوانیم نامهای میانی را هم بپذیریم. بیایید get_formatted_name() را طوری تغییر دهیم که در آن نامهای میانی اختیاری باشند و سپس آزمون را دوباره اجرا کنیم. اگر موفّق باشد، در ادامه مطمئن میشویم که تابع، نامهای میانی را هم به خوبی مدیریّت میکند.
برای اختیاری کردن نامهای میانی پارامتر middle را به انتهای فهرست پارامترها منتقل کرده و به آن یک مقدار پیشفرض خالی میدهیم. همچنین یک آزمون if اضافه میکنیم که بسته به وجود داشتن یا نداشتن نام میانی، نام کامل را به درستی ایجاد میکند:
defget_formatted_name(first, last, middle=''):
"""Generate a neatly formatted full name."""if middle:
full_name =f"{first}{middle}{last}"else:
full_name =f"{first}{last}"return full_name.title()
در نسخهی جدید get_formatted_name() نام میانی اختیاری است. اگر نام میانی به تابع داده شود نام کامل شامل نام، نام میانی و نام خانوادگی خواهد بود. در غیر این صورت نام کامل تنها از نام و نام خانوادگی درست خواهد شد. حال تابع باید با هر دو نوع اسم کار کند. برای دریافتنِ این که تابع همچنان با نامهایی مانند Janis Joplin کار میکند، test_name_function.py را دوباره اجرا کنید:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
آزمون اکنون موفّق است. این حالت ایدهآل است زیرا بدون آن که نیاز باشد تابع را به صورت دستی آزمایش کنیم، میفهمیم که تابع برای نامهایی مانند Janis Joplin هم کار میکند. اصلاحِ تابع به این دلیل ساده بود که شکست خوردن در آزمون به ما کمک کرد تا بفهمیم کد جدید رفتار پیشین را مختل کرده است.
11.4 - افزودن آزمونهای جدید
حال که مطمئن شدیم get_formatted_name() با نامهای ساده هم کار میکند، آزمون دیگری برای افرادی مینویسیم که نام میانی دارند. این کار را با اضافه کردن متدی دیگر به کلاس NamesTestCase انجام میدهیم:
classNamesTestCase(unittest.TestCase):
"""Tests for 'name_function.py'."""deftest_first_last_name(self):
--snip--deftest_first_last_middle_name(self):
"""Do names like 'Wolfgang Amadeus Mozart' work?""" formatted_name = get_formatted_name(
'wolfgang', 'mozart', 'amadeus')
self.assertEqual(formatted_name, 'Wolfgang Amadeus Mozart')
if __name__ =='__main__':
unittest.main()
نام این متد جدید را test_first_last_middle_name() میگذاریم. نام متد باید با test_ شروع شود تا هنگام اجرای test_name_function.py به طور خودکار اجرا شود. متد را طوری نامگذاری میکنیم که مشخّص باشد کدام رفتار get_formatted_name() را آزمایش میکنیم. در نتیجه اگر آزمون شکست بخورد بلافاصله میفهمیم که کدام نوع نامها تحت تاثیر قرار گرفتهاند. طولانی بودن نام متدها در کلاسهای TestCase اشکالی ندارد. نام آنها باید گویا باشد تا هنگام شکست خوردن آزمون خروجی برای شما معنادار باشد؛ به جز این، چون پایتون آنها را به طور خودکار صدا میکند، هیچگاه به نوشتن کدی برای صدا کردن آنها نیاز نداریم.
برای آزمودن تابع get_formatted_name()، آن را با نام، نام میانی و نام خانوادگی فراخوانی میکنیم و پس از آن از assertEqual() استفاده میکنیم تا تطابق خروجی تابع و نام کاملی را که انتظار داریم، بررسی کنیم. زمانی که دوباره test_name_function.py را اجرا میکنیم هر دو آزمون قبول میشوند:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
بسیار خوب! اکنون میدانیم که تابع همچنان با نامهایی مانند Janis Joplin کار میکند و علاوه بر آن مطمئنیم که نامهایی چون Wolfgang Amadeus Mozart هم در عملکرد آن خللی وارد نمیکنند.
خودآزمایی
۱۱-۱. شهر، کشور: تابعی بنویسید که دو پارامتر دریافت کند: نام یک شهر و نام یک کشور. این تابع باید رشتهای به شکل City, Country برگرداند، مانند Santiago, Chile. تابع را در ماژولی به نام city_functions.py ذخیره کنید.
فایلی به نام test_cities.py برای آزمودنِ تابعی که هماکنون نوشتهاید ایجاد کنید. (به خاطر بیاورید که باید unittest و تابع خود را وارد کنید.) متدی به نام test_city_country() بنویسید که فراخوانی تابع را با مقدارهایی مثل 'santiago' و 'chile' بیازماید تا بررسی کند که رشتهی صحیح را تولید میکند یا نه. test_cities.py را اجرا کنید و مطمئن شوید که test_city_country() موفّقیّتآمیز باشد.
۱۱-۲. جمعیّت: تابع را تغییر دهید تا پارامتر سومی را به نام population بپذیرد. این تابع الان باید رشتهای به شکل City, Country – population xxx برگرداند، مانند Santiago, Chile – population 5000000. فایل test_cities.py را دوباره اجرا کنید و مطمئن شوید که این بار شکست میخورد.
تابع را طوری تغییر دهید که پارامتر population در آن اختیاری باشد. فایل test_cities.py را دوباره اجرا کنید و مطمئن شوید که test_city_country() دوباره قبول میشود.
آزمون دومی به نام test_city_country_population() بنویسید که امکان فراخوانی تابع را با مقدارهای 'santiago'، 'chile' و population=5000000 درستیسنجی کند. فایل test_cities.py را دوباره اجرا کنید و مطمئن شوید که آزمون جدید موفّق است.
11.5 - آزمودن یک کلاس
در بخش یکم این فصل آزمونهایی برای یک تابع تکّی نوشتید. اکنون آزمونهایی برای یک کلاس مینویسید. شما در برنامههای خود از کلاسها به دفعات استفاده میکنید بنابراین امکانِ ثابت کردنِ کارکردِ درستِ کلاسها سودمند است. اگر آزمونهای کلاسی که با آن کار میکنید موفّق باشد، میتوانید مطمئن باشید توسعه دادن کلاس به طور ناگهانی رفتار فعلی آن را با مشکل روبهرو نمیکند.
گسترهای از متدهای اثبات
پایتون در کلاس unittest.TestCase چند متد اثبات ارائه میکند. چنانکه پیشتر اشاره شد آزمونهای اثبات بررسی میکنند که آیا شرطی که در نقطهی خاصّی از برنامه به درستیِ آن اعتقاد دارید، واقعاً درست است یا نه. اگر شرط، مطابقِ انتظارِ شما درست باشد فرضیّهی شما در بارهی رفتار آن بخش از برنامه تایید میشود پس میتوانید مطمئن باشید که خطایی وجود ندارد. اگر شرطی که فرض میکنید درست است، نادرست باشد پایتون اعلام استثنا میکند.
جدول ۱۱-۱ شش متد اثبات متداول را شرح میدهد. با این متدها میتوانید بررسی کنید که مقدارهای بازگشتی مساوی یا مخالف مقدارهای مورد انتظار است، یا مقدارها درست یا نادرستاند، یا در یک لیست وجود دارد (in) یا نه (not in). از این متدها تنها در کلاسی میتوانید استفاده کنید که از unittest.TestCase ارث برده باشد، بنابراین اجازه دهید که در بستر آزمودن یک کلاس واقعی به نحوهی کاربرد یکی از این متدها، نگاهی بیندازیم.
متد
کاربرد
assertEqual(a, b)
درستیسنجی a == b
assertNotEqual(a, b)
درستیسنجی a != b
assertTrue(x)
درستیسنجی این که x درست است
assertFalse(x)
درستیسنجی این که x نادرست است
assertIn(item, list)
درستیسنجی این که item در list هست یا نه
assertNotIn(item, list)
درستیسنجی این که item در list نیست
کلاسی برای آزمودن
آزمودن یک کلاس مشابه آزمودن یک تابع است— بیشتر کار شما به آزمودن متدهای یک کلاس معطوف میشود. با این حال تفاوتهای کوچکی نیز وجود دارد بنابراین بیاید کلاسی برای آزمودهشدن بنویسیم. کلاسی را در نظر بگیرید که به مدیریّت نظرات ناشناس کمک میکند:
classAnonymousSurvey:
"""Collect anonymous answers to a survey question."""def__init__(self, question):
"""Store a question, and prepare to store responses.""" self.question = question
self.responses = []
defshow_question(self):
"""Show the survey question.""" print(self.question)
defstore_response(self, new_response):
"""Store a single response to the survey.""" self.responses.append(new_response)
defshow_results(self):
"""Show all the responses that have been given.""" print("Survey results:")
for response in self.responses:
print(f"- {response}")
این کلاس با پرسشی که برای نظرسنجی ارائه میکنید، شروع میشود و شامل لیستی خالی برای ذخیره کردن پاسخهاست. این کلاس متدهایی برای چاپ کردن سوال نظرسنجی، افزودن پاسخ جدید به لیست پاسخها و چاپ کردن تمام پاسخهای موجود در لیست دارد. برای ساختن نمونهای از کلاس کافی است به آن پرسشی ارائه کنید. با وجود یک نمونه که بیانگر نظرسنجی خاصّی باشد، میتوانید پرسش نظرسنجی را با show_question() نشان دهید، پاسخ را به کمک store_response() ذخیره کنید و نتایج را با show_results() نمایش دهید.
برای نمایش کارکرد درست AnonymousSurvey برنامهای مینویسیم که از آن استفاده کند:
from survey import AnonymousSurvey
# Define a question, and make a survey.question ="What language did you first learn to speak?"my_survey = AnonymousSurvey(question)
# Show the question, and store responses to the question.my_survey.show_question()
print("Enter 'q' at any time to quit.\n")
whileTrue:
response = input("Language: ")
if response =='q':
break my_survey.store_response(response)
# Show the survey results.print("\nThank you to everyone who participated in the survey!")
my_survey.show_results()
این برنامه پرسشی طرح میکند ("What language did you first learn to speak?") و یک شیء AnonymousSurvey با آن پرسش میسازد. برنامه show_question() را برای نمایش پرسش فراخوانی و سپس پاسخها را دریافت میکند. هر کدام از پاسخها به محض دریافت ذخیره میشوند. پس از وارد شدنِ تمام پرسشها (وقتی کاربر q را برای خروج وارد میکند)، متد show_results() نتایج نظرسنجی را چاپ میکند:
What language did you first learn to speak?
Enter 'q' at any time to quit.
Language: English
Language: Spanish
Language: English
Language: Mandarin
Language: q
Thank you to everyone who participated in the survey!
Survey results:
- English
- Spanish
- English
- Mandarin
این کلاس برای نظرسنجیهای ناشناسِ ساده کارآمد است. امّا فرض کنید میخواهید AnonymousSurvey و ماژول دربرگیرندهی آن survey را توسعه دهیم. میتوانیم به هر کاربر اجازهی وارد کردن بیش از یک پاسخ بدهیم. میشود متدی نوشت که تنها پاسخهای یکتا را فهرست کند و گزارش کند که هر پاسخ چند بار تکرار شده است. میتوانیم کلاس دیگری برای مدیریّت نظرسنجیهای با نام بنویسیم.
پیادهسازی چنین تغییراتی این خطر را دارد که رفتار فعلی AnonymousSurvey را تحتالشعاع قرار دهد. برای مثال ممکن است حین تلاش برای دریافت چند پاسخ از یک کاربر، اشتباهاً نحوهی رسیدگی به پاسخهای تکّی را تغییر دهیم. برای این که مطمئن شویم همزمان با توسعهی این ماژول، رفتار موجود دست نمیخورد میتوانیم چند آزمون برای کلاس بنویسیم.
آزمودن کلاس AnonymousSurvey
اکنون آزمونی مینویسیم که یک جنبه از رفتار AnonymousSurvey را درستیسنجی کند. آزمونی که مینویسیم بررسی میکند که آیا پاسخِ دادهشده به پرسش نظرسنجی، به درستی ذخیره میشود یا نه. از متد assertIn() استفاده میکنیم تا پس از ذخیره کردن، وجود پاسخ را در لیستِ پاسخها بررسی کند:
import unittest
from survey import AnonymousSurvey
classTestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey"""deftest_store_single_response(self):
"""Test that a single response is stored properly.""" question ="What language did you first learn to speak?" my_survey = AnonymousSurvey(question)
my_survey.store_response('English')
self.assertIn('English', my_survey.responses)
if __name__ =='__main__':
unittest.main()
از وارد کردنِ ماژول unittest و کلاس مورد آزمایش، AnonymousSurvey، شروع میکنیم. نام مورد آزمایشی را TestAnonymousSurvey میگذاریم که مجدّداً از unittest.TestCase ارث میبرد. اوّلین متد آزمایش بررسی میکند که پاسخ ارائهشده به پرسش نظرسنجی، پس از ذخیره شدن، در لیست پاسخهای نظرسنجی میآید یا نه. یک نام گویای خوب برای چنین متدی test_store_single_response() است. اگر این آزمون شکست بخورد از نام متدی که در خروجی آزمون میآید میفهمیم که مشکل در ذخیره کردن پاسخهای تکّی نظرسنجی بوده است.
برای آزمودن رفتار کلاس باید نمونهای از آن بسازیم. نمونهای به نام my_survey با پرسش "What language did you first learn to speak?" میسازیم. یک پاسخ تکّی، English، را به کمک متد store_response() ذخیره میکنیم. پس از آن با اثبات وجود English در لیست my_survey.responses، ذخیره شدن درست پاسخ را درستیسنجی میکنیم.
اجرای آزمون test_survey.py همراه با موفّقیّت خواهد بود:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
این خوب است امّا نظرسنجی زمانی سودمند است که بیش از یک پاسخ تولید کند. اکنون بررسی میکنیم که آیا سه پاسخ هم به درستی ذخیره میشود یا نه. برای چنین کاری متدی دیگر به TestAnonymousSurvey اضافه میکنیم:
import unittest
from survey import AnonymousSurvey
classTestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey"""deftest_store_single_response(self):
--snip--deftest_store_three_responses(self):
"""Test that three individual responses are stored properly.""" question ="What language did you first learn to speak?" my_survey = AnonymousSurvey(question)
responses = ['English', 'Spanish', 'Mandarin']
for response in responses:
my_survey.store_response(response)
for response in responses:
self.assertIn(response, my_survey.responses)
if __name__ =='__main__':
unittest.main()
متد جدید test_store_three_responses() را صدا میکنیم. یک شیء نظرسنجی مانند آنچه در test_store_single_response() دیدیم ایجاد میکنیم. لیستی شامل سه پاسخ مختلف درست میکنیم، سپس برای هر کدام store_response() را فرامیخوانیم. به محض ذخیرهشدن پاسخها حلقهی دیگری مینویسیم که وجود هر پاسخ را در my_survey.responses تایید کند.
حال test_survey.py را دوباره اجرا میکنیم و هر دو آزمون (برای پاسخ تکّی و پاسخ سهتایی) قبول میشود:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
این آزمون بسیار عالی کار میکند. با این حال این آزمونها کمی تکراریاند بنابراین از ویژگی دیگر unittest برای کاراتر کردن آنها استفاده میکنیم.
11.6 - متد setUp()
در test_survey.py در هر متدِ آزمون، یک نمونه از AnonymousSurvey و مجموعهای از پاسخهای جدید را در هر متد ساختیم. کلاس unittest.TestCase متدی به نام setUp() دارد که به شما مکان میدهد این اشیا را یک بار بسازید و سپس از آنها در هر کدام از متدهای آزمون استفاده کنید. زمانی که متد setUp() در یک کلاس TestCase میآورید، پایتون آن را پیش از اجرای هر متد آزمونی که با test_ شروع میشود، اجرا میکند. هر شیءای پس از ساختهشدن در متد setUp()، در تمام متدهای آزمون در دسترس خواهد بود.
بیایید از setUp() برای ساختن یک نمونهی نظرسنجی و مجموعهای از پاسخها استفاده کنیم تا بتوانیم از آنها در test_store_single_response() و test_store_three_responses() استفاده کنیم:
import unittest
from survey import AnonymousSurvey
classTestAnonymousSurvey(unittest.TestCase):
"""Tests for the class AnonymousSurvey."""defsetUp(self):
"""Create a survey and a set of responses for use in all test methods.""" question ="What language did you first learn to speak?" self.my_survey = AnonymousSurvey(question)
self.responses = ['English', 'Spanish', 'Mandarin']
deftest_store_single_response(self):
"""Test that a single response is stored properly.""" self.my_survey.store_response(self.responses[0])
self.assertIn(self.responses[0], self.my_survey.responses)
deftest_store_three_responses(self):
"""Test that three individual responses are stored properly."""for response in self.responses:
self.my_survey.store_response(response)
for response in self.responses:
self.assertIn(response, self.my_survey.responses)
if __name__ =='__main__':
unittest.main()
متد setUp() دو کار انجام میدهد: یک شیء نمونهی نظرسنجی میسازد و لیستی از پاسخها ایجاد میکند. هر دو پیشوند self گرفتهاند پس میتوان از آنها هر جای کلاس استفاده کرد. این کار نوشتنِ دو متد را سادهتر میکنند چون هیچکدام دیگر به ساختن نمونهی نظرسنجی یا لیست پاسخها نیازی ندارند. متد test_store_single_response() ذخیره شدن اوّلین پاسخ را در self.responses[0] بررسی میکند و test_store_three_responses() ذخیره شدن درست هر سه پاسخ موجود را در self.responses درستیسنجی میکند.
اگر test_survey.py را دوباره اجرا کنیم هر دو آزمون موفّق خواهند بود. این آزمونها به ویژه زمانی سودمندند که بخواهیم AnonymousSurvey را برای رسیدگی به چند پاسخِ یک کاربر توسعه دهیم. پس از تغییر دادن کد برای دریافت چند پاسخ از یک کاربر، میتوانید این آزمونها را دوباره اجرا کنید تا مطمئن شوید که توانایی ذخیرهسازی یک پاسخ تکّی یا مجموعه از پاسخهای جدا تحتالشعاع قرار نگرفته باشد.
وقتی کلاسهای خود را آزمایش میکنید متد setUp() نوشتن متدهای آزمون را سادهتر میکند. یک مجموعه از نمونهها و مشخّصهها را در setUp() میسازید و از آنها در تمام متدهای آزمون خود استفاده میکنید. این کار از ساختنِ چندبارهی این نمونهها در هر متد آزمون سادهتر خواهد بود.
نکته
زمانی که مورد آزمایشی در حال اجراست، پایتون پس از پایان هر آزمون واحد یک کاراکتر چاپ میکند؛ برای آزمون موفّق یک نقطه، برای آزمونی که به خطا برمیخورد یک E و برای آزمونی که به رد شدن یک متد اثبات میانجامد یک F. به این دلیل است که هنگام اجرای موردهای آزمایشی تعداد مختلفی نقطه و کاراکتر در خطّ اوّل میبینید. اگر اجرای یک مورد آزمایشی به دلیل تعدّد متدهای آزمون زمان زیادی ببرد میتوانید با نگاه کردن به این نتایج درکی کلّی از تعداد آزمونهای در حال انجام و قبول یا رد شدنِ آنها پیدا کنید.
خودآزمایی
۱۱-۳. کارمند: کلاسی به نام Employee بنویسید. متد __init__() باید یک نام، نام خانوادگی و حقوق سالیانه دریافت کند و آنها را به عنوان مشخّصه ذخیره نماید. متدی به نام give_raise() بنویسید که به طور پیشفرض ۵۰۰۰ دلار به حقوق سالیانه اضافه کند امّا مقادیر افزایشِ حقوقِ دیگر را هم در صورت وجود بپذیرد.
یک مورد آزمایشی برای Employee بنویسید. دو متد آزمون بنویسید، test_give_default_raise() و test_give_custom_raise(). از متد setUp() استفاده کنید تا مجبور نباشید در هر کدام از متدها یک شیء نمونهی کارمند بسازید. مورد آزمایشی خود را اجرا کنید و مطمئن شوید که هر دو آزمون موفّق خواهند بود.
خلاصه
در این فصل آموختید که چگونه به کمک ابزارهای موجود در ماژول unittest برای تابعها و کلاسهای خود آزمون بنویسید. نوشتن کلاسی را که از unittest.TestCase ارث میبرد یاد گرفتید و این که چگونه برای درستیسنجی رفتارهای خاصّ تابعها و کلاسهای خود آزمون بنویسید. بهکارگیری متد setUp() را برای ساختن کارآمدتر نمونهها و مشخّصهها از کلاس خود آموختید تا از آنها در تمام متدهای آزمون یک کلاس استفاده کنید.
فرآیند آزمودن موضوع مهمّی است که بیشتر تازهکارها یاد نمیگیرند. نیازی نیست که برای تمام پروژههای سادهای که به عنوان یک تازهکار مینویسید، آزمون بنویسید. امّا به محض شروع کار در پروژههایی که توسعههای چشمگیر و پرزحمتی را در برمیگیرند، باید رفتارهای حیاتی تابعها و کلاسهای خود را بیازمایید. این کار شما را مطمئن میکند که کار جدید شما در پروژه، کارهای قبلی را مختل نکرده است و به شما برای ایجاد توسعه در کد خود آزادی عمل میدهد. اگر تصادفاً قابلیّتی را که قبلاً وجود داشته دچار مشکل کنید، بلافاصله خواهید فهمید بنابراین میتوانید به راحتی آن را اصلاح کنید. حل کردن مشکل یک آزمون ناموّفق بسیار سادهتر از آن است که به گزارش اشکال یک کاربر ناراضی پاسخ دهید.
اگر چند آزمون ابتدایی در پروژههای خود داشته باشید، دیگر برنامهنویسان برای آنها ارزش بیشتری قایل خواهند بود. آنها برای آزمایش کردن کد شما احساس راحتی بیشتری میکنند و به همکاری با شما رغبت بیشتری نشان خواهند داد. اگر بخواهید در پروژهای که برنامهنویسان دیگری هم در آن مشغول هستند مشارکت کنید، آنها انتظار دارند که نشان دهید کد شما آزمونهای موجود را رد میکند و معمولاً از شما انتظار میرود که برای رفتارهای جدید که به پروژه اضافه میکنید آزمون بنویسید.
سر و کلّه زدن با آزمونها برای آشنا شدن با آنها بخشی از فرآیند آزمون کد شماست. برای حیاتیترین رفتارهای تابعها و کلاسهای خود آزمون بنویسید امّا در پروژههای اوّل خود دنبالِ پوشش کامل نباشید مگر این که دلیل خاصّی برای آن داشته باشید.