حل مسئله
تا اینجا بخش های مختلف زبان پایتون را بررسی کردیم و اکنون می خواهیم ببینیم چگونه همۀ این بخش ها در کنار یکدیگر قرار می گیرند؛ به این منظور، برنامه ای طراحی و پیاده سازی خواهیم کرد که مفید واقع شود. هدف این است که یاد بگیریم چگونه به تنهایی یک اسکریپت پایتون بنویسیم.
مسئله
مسئله ای که می خواهیم حل کنیم این است:
برنامه ای می خواهم که از تمام فایل های مهم من یک نسخۀ پشتیبان تهیه کند.
اگرچه این یک برنامۀ ساده است، اطلاعات کافی برای شروع در اختیار ما قرار نمی دهد. بنابراین، به مقدار بیشتری تحلیل نیاز داریم. برای مثال، چگونه مشخص کنیم کدام فایل ها باید پشتیبان گیری شوند؟ چگونه ذخیره شوند؟ کجا ذخیره شوند؟
پس از تحلیل دقیق مسئله، برنامۀ خود را طراحی می کنیم. لیستی از نحوۀ عملکرد برنامه تهیه می کنیم. در این مورد، من لیست زیر را بر اساس نحوه ای که می خواهم برنامه عمل کند، تهیه کرده ام. اگر شما این طراحی را انجام دهید، ممکن است به همان نوع تحلیل نرسید، زیرا هر فرد روش خاص خودش را برای انجام کارها دارد؛ بنابراین این موضوع کاملاً طبیعی است.
- فایل ها و پوشه هایی که باید از آنها نسخۀ پشتیبان تهیه کرد، در یک لیست مشخص می شوند.
- نسخۀ پشتیبان باید در یک پوشۀ اصلی پشتیبان ذخیره شود.
- از فایل ها در قالب یک فایل zip نسخۀ پشتیبان تهیه می شود.
- نام بایگانی zip، تاریخ و زمان فعلی خواهد بود.
- از دستور استاندارد
zipاستفاده می کنیم که به طور پیش فرض در هر توزیع استاندارد GNU/Linux یا Unix در دسترس است. توجه داشته باشید که می توانید از هر دستور بایگانی که می خواهید استفاده کنید، به شرط آنکه رابط خط فرمان داشته باشد.
برای کاربران ویندوز
کاربران ویندوز می توانند دستور
zipرا از GnuWin32 project page بر روی رایانه خود نصب کنند و مسیرC:\Program Files\GnuWin32\binرا به متغیر محیطیPATHسیستم خود اضافه کنند؛ مشابه کاری که برای شناسایی خود دستور پایتون انجام دادیم .
راه حل
اکنون که طراحی برنامۀ ما تا حد قابل قبولی تثبیت شده است، می توانیم کدی که پیاده سازی راه حل ماست، بنویسیم.
با نام backup_ver1.py ذخیره کنید:
import os
import time
# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we have to use double quotes inside a string
# for names with spaces in it. We could have also used
# a raw string by writing [r'C:\My Documents'].
# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using
# 3. The files are backed up into a zip file.
# 4. The name of the zip archive is the current date and time
target = target_dir + os.sep + \
time.strftime('%Y%m%d%H%M%S') + '.zip'
# Create target directory if it is not present
if not os.path.exists(target_dir):
os.mkdir(target_dir) # make directory
# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
' '.join(source))
# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
خروجی:
$ python backup_ver1.py
Zip command is:
zip -r /Users/swa/backup/20140328084844.zip /Users/swa/notes
Running:
adding: Users/swa/notes/ (stored 0%)
adding: Users/swa/notes/blah1.txt (stored 0%)
adding: Users/swa/notes/blah2.txt (stored 0%)
adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140328084844.zip
اکنون وارد مرحلۀ آزمون شدیم؛ در این مرحله بررسی می کنیم که آیا برنامۀ ما به درستی کار می کند یا خیر. اگر برنامه مطابق انتظار رفتار نکرد، باید آن را اشکال زدایی (debug) کنیم؛ یعنی باگ های (خطاهای) موجود در برنامه را بر طرف کنیم.
اگر برنامۀ بالا برای شما کار نکرد، خطی را که پس از عبارت zip command is در خروجی چاپ شده است کپی کنید و آن را در shell (در GNU/Linux و Mac OS X) یا cmd (در ویندوز) قرار دهید، ببینید خطا چیست و سعی کنید آن را بر طرف کنید. همچنین راهنمای دستور zip را بررسی کنید تا ببینید مشکل احتمالی چیست. اگر این دستور با موفقیت اجرا شد، ممکن است مشکل در خود برنامۀ پایتون باشد؛ بنابراین بررسی کنید که برنامه دقیقاً با برنامه ای که در بالا نوشته شده است مطابقت داشته باشد.
نحوۀ عملکرد
متوجه خواهید شد که چطور طراحی خود را مرحله به مرحله به کد تبدیل کردیم.
ابتدا با وارد کردن ماژول های os و time از آنها استفاده می کنیم. سپس فایل ها و پوشه هایی را که باید از آنها نسخۀ پستیبان تهیه شود، در لیست source مشخص می کنیم. پوشۀ مقصد، محلی است که تمام فایل های پشتیبان را در آن ذخیره می کنیم و این پوشه در متغیر target_dir مشخص شده است. نام بایگانی zip که قصد ایجاد آن را داریم، تاریخ و زمان فعلی است که با استفاده از تابع ()time.strftime تولید می کنیم. این فایل همچنین دارای پسوند zip. خواهدبود و در پوشۀ target_dir ذخیره خواهد شد.
به استفاده از متغیر os.sep توجه کنید؛ این متغیر جداکنندۀ پوشه ها را متناسب با سیستم عامل شما ارائه می کند؛ یعنی در GNU/Linux, Unix, macOS '/' خواهد بود و در ویندوز '//' خواهد بود. استفاده از os.sep به جای نوشتن مستقیم این کاراکترها باعث می شود برنامه ما قابل انتقال باشد و در تمام این سیستم ها کار کند.
تابع ()time.strftime مشخصه ای مانند آنچه در برنامۀ بالا استفاده کردیم دریافت می کند. مشخصۀ Y% با سال به همراه قرن جایگزین می شود. مشخصۀ m% با ماه به صورت یک عدد اعشاری بین 01 و 12 جایگزین می شود و به همین ترتیب ادامه پیدا می کند. فهرست کامل این مشخصه ها را می توانید در راهنمای مرجع پایتون مشاهده کنید.
نام فایل zip مقصد را با استفاده از عملگر جمع ایجاد می کنیم که رشته ها را به یکدیگر الحاق می کند؛ یعنی دو رشته را به هم متصل کرده و یک رشتۀ جدید بر می گرداند. سپس رشته ای با نام zip_command ایجاد می کنیم که شامل دستوری است که قصد اجرای آن را داریم. می توانید با اجرای این دستور در shell (ترمینال GNU/Linux یا خط فرمان DOS) بررسی کنید که آیا به درستی کار می کند یا خیر.
دستور zip که استفاده می کنیم، چند گزینه دارد که یکی از آنها r- است. گزینۀ r- مشخص می کند که دستور zip باید به صورت بازگشتی (recursively) برای پوشه ها عمل کند؛ یعنی شامل تمام زیرپوشه ها و فایل های موجود نیز می شود. پس از گزینه ها، نام بایگانی zip که باید ایجاد شود و سپس لیست فایل ها و پوشه هایی که باید از آن ها نسخۀ پشتیبان تهیه شود قرار می گیرد. لیست source را با استفاده از روش join مربوط به رشته ها، که پیش تر نحوۀ استفاده از آنها را دیده ایم، به یک رشته تبدیل می کنیم.
در ادامه، سرانجام با استفاده از تابع os.system دستور را اجرا می کنیم. این تابع دستور را به گونه ای اجرا می کند که گویی از خود سیستم یعنی در shell، اجرا شده است. اگر اجرای دستور موفقیت آمیز باشد، مقدار 0 را بر می گرداند . در غیر این صورت یک شمارۀ خطا بر می گرداند.
بسته به خروجی دستور، پیام مناسبی پرینت می کنیم که نشان می دهد عملیات پشتیبان گیری ناموفق بوده یا با موفقیت انجام شده است.
همین، ما یک اسکریپت ایجاد کردیم که از فایل های مهم ما نسخۀ پشتیبان تهیه می کند!
نکته ای برای کاربران ویندوز
به جای استفاده از دنباله های escape شامل دو بک اسلش، می توانید از رشته های خام نیز استفاده کنید. برای مثال، از
'C:\\Documents'یا'r'C:\Documentsاستفاده کنید. با این حال، از'C:\Documents'استفاده نکنید، زیرا در این حالت از یک دنبالۀ escape ناشناخته، یعنیD\، استفاده خواهید کرد.
اکنون که یک اسکریپت پشتیبان گیری عملیاتی در اختیار داریم، می توانیم هر زمان که بخواهیم از فایل های خود نسخۀ پشتیبان تهیه کنیم. این مرحله، مرحلۀ عملیات یا مرحلۀ استقرار نرم افزار نامیده می شود.
برنامۀ بالا به طور دقیق کار می کند، اما برنامه های اولیه (معمولاً) دقیقاً مطابق انتظار شما عمل نمی کنند. برای مثال، اگر برنامه را به درستی طراحی نکرده باشید یا هنگام تایپ کد اشتباهی کرده باشید و مواردی همچون این موارد، مشکلاتی ایجاد می شوند. در چنین شرایطی، باید به مرحلۀ طراحی بازگردید یا باگ های برنامۀ خود را رفع کنید.
نسخۀ دوم
نسخۀ اول اسکریپت ما کار می کند. با این حال، می توانیم اصلاحاتی را در آن انجام دهیم تا برای استفادۀ روزمرره عملکرد بهتری داشته باشد. این مرحله، مرحلۀ نگهداری نرم افزار نام دارد.
یکی از اصلاحاتی که در نظر من مفید بود، استفاده از ساز و کار بهتری برای نام گذاری فایل هاست؛ به این صورت که از زمان (time) به عنوان نام فایل درون پوشه ای استفاده کنیم که تاریخ فعلی نام آن پوشه باشد و این پوشه نیز درون پوشۀ اصلی پشتیبان قرار داشته باشد. مزیت اول این است که نسخه های پشتیبان شما به شکل سلسله مراتبی ذخیره می شوند و در نتیجه مدیریت آنها بسیار آسان تر خواهد بود. مزیت دوم این است که نام فایل ها بسیار کوتاه تر می شوند. مزیت سوم این است که پوشه های جداگانه به شما کمک می کنند بررسی کنید که آیا برای هر روز نسخۀ پشتیبان تهیه کرده اید یا خیر؛ زیرا پوشه تنها در صورتی ایجاد می شود که در آن روز نسخۀ پشتیبان تهیه کرده باشید.
با نام backup_ver2.py ذخیره کنید:
import os
import time
# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.
# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using
# Create target directory if it is not present
if not os.path.exists(target_dir):
os.mkdir(target_dir) # make directory
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')
# The name of the zip file
target = today + os.sep + now + '.zip'
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today)
print('Successfully created directory', today)
# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
' '.join(source))
# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
خروجی:
$ python backup_ver2.py
Successfully created directory /Users/swa/backup/20140329
Zip command is:
zip -r /Users/swa/backup/20140329/073201.zip /Users/swa/notes
Running:
adding: Users/swa/notes/ (stored 0%)
adding: Users/swa/notes/blah1.txt (stored 0%)
adding: Users/swa/notes/blah2.txt (stored 0%)
adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140329/073201.zip
نحوۀ عملکرد
بخش عمدۀ برنامه همچنان بدون تغییر باقی مانده است. تغییرات این است که با استفاده از تابع os.path.exists بررسی می کنیم آیا درون پوشۀ اصلی پشتیبان، پوشه ای با نام روز فعلی وجود دارد یا خیر. اگر وجود نداشته باشد، آن را با استفاده از تابع os.mkdir ایجاد می کنیم.
نسخۀ سوم
نسخۀ دوم هنگامی که نسخه های پشتیبان زیادی تهیه میکنم به خوبی کار می کند، اما وقتی تعداد پشتیبان ها زیاد می شود، تشخیص اینکه هر نسخۀ پشتیبان مربوط به چه کاری بوده برایم دشوار است! برای مثال، ممکن است تغییراتی اساسی در یک برنامه یا ارائه ایجاد کرده باشم و سپس بخواهم این تغییرات را با نام بایگانی zip مرتبط کنم. این کار را می توان به سادگی با افزودن یک توضیح وارد شده توسط کاربر به نام بایگانی zip انجام داد.
هشدار: برنامه زیر کار نمی کند، بنابراین نگران نشوید؛ ادامه دهید، زیرا نکته ای برای یادگیری در اینجا وجود دارد.
تحت عنوان backup_ver3.py ذخیره کنید:
import os
import time
# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.
# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using
# Create target directory if it is not present
if not os.path.exists(target_dir):
os.mkdir(target_dir) # make directory
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')
# Take a comment from the user to
# create the name of the zip file
comment = input('Enter a comment --> ')
# Check if a comment was entered
if len(comment) == 0:
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' +
comment.replace(' ', '_') + '.zip'
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today)
print('Successfully created directory', today)
# 5. We use the zip command to put the files in a zip archive
zip_command = "zip -r {0} {1}".format(target,
' '.join(source))
# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
خروجی:
$ python backup_ver3.py
File "backup_ver3.py", line 39
target = today + os.sep + now + '_' +
^
SyntaxError: invalid syntax
چگونه این برنامه کار نمی کند
این برنامه کار نمی کند! پایتون اعلام می کند که یک خطای نحوی (syntax error) وجود دارد؛ یعنی اسکریپت ساختاری را که پایتون انتظار دارد مشاهده کند را رعایت نکرده است. هنگامی که خطایی را که پایتون اعلام کرده بررسی می کنیم، محل تشخیص خطا را نیز به ما نمایش می دهد. بنابراین، اشکال زدایی برنامه خود را از همان خط آغاز می کنیم.
با دقت بیشتر متوجه می شویم که یک خط منطقی به دو خط فیزیکی تقسیم شده است، اما مشخص نکرده ایم که این دو خط فیزیکی به یکدیگر تعلق دارند. در واقع، پایتون عملگر جمع (+) را بدون هیچ عملوندی در آن خط منطقی پیددا کرده است و بنابراین نمی داند که چگونه به کار خود ادامه دهد. به خاطر داشته باشید که می توانیم با استفاده از یک بک اسلش در آخر خط فیزیکی مشخص کنیم که خط منطقی در خط فیزیکی بعدی ادامه پیدا می کند. بنابراین، این اصلاح را در برنامه انجام می دهیم. اصلاح برنامه هنگام یافتن خطاها رفع باگ (bug fixing) نام دارد.
نسخۀ چهارم
با نام backup_ver4.py ذخیره کنید:
import os
import time
# 1. The files and directories to be backed up are
# specified in a list.
# Example on Windows:
# source = ['"C:\\My Documents"', 'C:\\Code']
# Example on Mac OS X and Linux:
source = ['/Users/swa/notes']
# Notice we had to use double quotes inside the string
# for names with spaces in it.
# 2. The backup must be stored in a
# main backup directory
# Example on Windows:
# target_dir = 'E:\\Backup'
# Example on Mac OS X and Linux:
target_dir = '/Users/swa/backup'
# Remember to change this to which folder you will be using
# Create target directory if it is not present
if not os.path.exists(target_dir):
os.mkdir(target_dir) # make directory
# 3. The files are backed up into a zip file.
# 4. The current day is the name of the subdirectory
# in the main directory.
today = target_dir + os.sep + time.strftime('%Y%m%d')
# The current time is the name of the zip archive.
now = time.strftime('%H%M%S')
# Take a comment from the user to
# create the name of the zip file
comment = input('Enter a comment --> ')
# Check if a comment was entered
if len(comment) == 0:
target = today + os.sep + now + '.zip'
else:
target = today + os.sep + now + '_' + \
comment.replace(' ', '_') + '.zip'
# Create the subdirectory if it isn't already there
if not os.path.exists(today):
os.mkdir(today)
print('Successfully created directory', today)
# 5. We use the zip command to put the files in a zip archive
zip_command = 'zip -r {0} {1}'.format(target,
' '.join(source))
# Run the backup
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
خروجی:
$ python backup_ver4.py
Enter a comment --> added new examples
Zip command is:
zip -r /Users/swa/backup/20140329/074122_added_new_examples.zip /Users/swa/notes
Running:
adding: Users/swa/notes/ (stored 0%)
adding: Users/swa/notes/blah1.txt (stored 0%)
adding: Users/swa/notes/blah2.txt (stored 0%)
adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140329/074122_added_new_examples.zip
نحوۀ عملکرد
این برنامه اکنون کار می کند! بیایید بهبودهایی را که در نسخۀ 3 ایجاد کرده ایم، بررسی کنیم. توضیحات کاربر را با استفاده از تابع input دریافت می کنیم و سپس با استفاده از تابع len طول ورودی را محاسبه می کنیم و بررسی می کنیم که آیا کاربر واقعاً چیزی را وارد کرده است یا خیر. اگر کاربر بدون وارد کردن چیزی کلید enter را بزند (شاید فقط یک پشتیبان گیری معمولی بوده یا تغییر خاصی ایجاد نشده باشد)، مانند قبل ادامه می دهیم.
با این وجود، اگر توضیحی وارد شده باشد، آن را درست پیش از پسوند zip. به نام آرشیو zip اضافه می کنیم. توجه داشته باشید که فاصله های موجود در توضیح را با زیرخط جایگزین می کنیم؛ دلیل این کار این است که مدیریت نام فایل هایی که فاقد فاصله هستند بسیار آسان تر است.
اصلاحات بیشتر
نسخۀ چهارم برای بیشتر کاربران اسکریپتی است که به طور رضایت بخشی کار می کند، اما همیشه جا برای بهبود وجود دارد. برای مثال، می توانید با مشخص کردن گزینۀ v- برای دستور zip، سطح verbosity یا میزان جزئیات خروجی را افزایش دهید تا برنامۀ شما اطلاعات بیشتری را نشان دهد، یا با استفاده از گزینۀ q- آن را ساکت کنید (تا خروجی کمتری ارائه دهد).
یکی دیگر از راه های بهبود ممکن این است که اجازه دهیم فایل ها و پوشه های اضافی از طریق خط فرمان به اسکریپت داده شوند. می توانیم نام این موارد را از لیست sys.argv تهیه کنیم و با استفاده از روش extend که توسط کلاس list ارائه شده است، آنها را به لیست source اضافه کنیم.
مهم ترین اصلاح این خواهد بود که به جای استفاده از روش os.system برای ایجاد بایگانی ها، از ماژول های داخلی zipfile یا tarfile برای ایجاد این بایگانی ها استفاده کنیم. این ماژول ها بخشی از کتابخانۀ استاندارد هستند و از قبل در اختیار شما قرار دارند و برای استفاده از آن ها نیازی به وابستگی خارجی به برنامۀ zip که باید بر روی کامپیوتر شما موجود باشد، نخواهید داشت.
با این حال، من در مثال های بالا صرفاً برای آموزش از روش os.system برای ایجاد نسخۀ پشتیبان استفاده کرده ام تا مثال به اندازۀ کافی ساده باشد که برای همه قابل درک باشد، اما در عین حال به اندازۀ کافی واقعی باشد که کاربردی و مفید نیز واقع شود.
آیا می توانید نسخۀ پنجم را خودتان بنویسید که به جای فراخوانی os.system از ماژول zipfile استفاده کند؟
فرآیند توسعۀ نرم افزار
اکنون از مراحل مختلف فرآیند نوشتن یک نرم افزار گذر کرده ایم. این مراحل را می توانیم به شکل زیر خلاصه سازی کنیم:
- چه چیزی (تحلیل)
- چگونه (طراحی)
- انجامش دهید (پیاده سازی)
- آزمون (آزمون و اشکال زدایی)
- استفاده (عملیات یا استقرار)
- نگهداری (اصلاح و بهبود)
یک روش پیشنهادی برای نوشتن برنامه ها، همان روشی است که به هنگام ایجاد اسکریپت پشتیبان گیری دنبال کردیم: تحلیل و طراحی را انجام دهید. با یک نسخۀ ساده پیاده سازی را شروع کنید. آن را آزمایش و اشکال زدایی کنید. از آن استفاده کنید تا مطمئن شوید مطابق انتظار کار می کند. اکنون، هر ویژگی ای را که می خواهید به آن اضافه کنید و چرخۀ انجامش دهید - آزمون - استفاده را به تعداد لازم تکرار کنید.
به خاطر داشته باشید:
نرم افزار رشد می کند، ساخته نمی شود. -- Bill de hÓra
جمع بندی
دیدیم که چگونه برنامه ها/ اسکریپت های پایتون خودمان را ایجاد کنیم و با مراحل مختلفی که در فرآیند نوشتن چنین برنامه هایی دخیل هستند آشنا شدیم. شاید برایتان مفید باشد که درست مانند کاری که در این فصل انجام دادیم، برنامۀ خودتان را ایجاد کنید تا هم با پایتون راحت تر شوید و هم مهارت حل مسئله خود را تقویت کنید.
در ادامه، به بررسی برنامه نویسی شئ گرا خواهیم پرداخت.