1 - خواندن یک فایل

حال که بر مهارت‌های مقدّماتیِ مورد نیاز برای نوشتنِ برنامه‌های سازمان‌یافته و با کاربریِ آسان تسلّط یافته‌اید، زمانِ فکر کردن به این موضوع رسیده است که چگونه برنامه‌هایتان را از این هم مناسب‌تر و قابل استفاده‌تر کنید. در این فصل کار کردن با فایل‌ها را خواهید آموخت تا برنامه‌هایتان بتوانند حجم زیادی از داده‌ها را به سرعت تحلیل کنند. مدیریّتِ خطاها را نیز می‌آموزید تا برنامه‌ها هنگام برخورد با شرایط پیش‌بینی‌نشده از کار نیفتند. درباره‌ی استثناها خواهید آموخت: شیءهای ویژه‌ای که پایتون هنگام اجرای برنامه می‌سازد تا خطاهای رخ‌داده را مدیریّت کند. همچنین درباره‌ی ماژول json خواهید آموخت که به کمک آن می‌توانید اطّلاعات کاربر را ذخیره کنید تا با توقّف برنامه از بین نروند.

آموختنِ نحوه‌ی کار کردن با فایل‌ها و ذخیره کردنِ داده‌ها، استفاده از برنامه‌های شما را برای کاربران آسان‌تر می‌کند. کاربران می‌توانند انتخاب کنند که چه داده‌هایی را در چه زمانی وارد کنند. افراد می‌توانند برنامه‌ی شما را اجرا کنند، قدری کار انجام دهند، آن را ببندند و مدّتی بعد کار خود را از همان‌جا دوباره ادامه دهند. مدیریّت استثناها به شما کمک می‌کند تا وضعیّت‌هایی را که فایلی وجود ندارد یا دیگر مشکلاتی را که می‌توانند به از کار افتادنِ برنامه‌ی شما منجر شوند، مدیریّت کنید. این کار برنامه‌های شما را هنگام برخوردن به داده‌های نامناسب، مقاوم‌تر می‌کند، چه این داده‌ها سهواً و اشتباهاً وارد شده باشند چه حاصلِ حمله‌های مخرّبی برای خراب کردنِ برنامه‌های شما باشند. با مهارت‌هایی که در این فصل می‌آموزید، برنامه‌های خود را کاربردی‌تر، کاراتر و پایدارتر می‌کنید.

حجم بسیار زیادی داده در فایل‌های متنی موجود است. فایل‌های متنی ممکن است شامل داده‌های هواشناسی، ترافیکی، اقتصادی-اجتماعی، ادبیّات و … باشند. خواندن داده‌ها از یک فایل به ویژه در کاربردهای مربوط به تحلیلِ داده سودمند است، امّا در بسیاری از وضعیّت‌ها که در آن‌ها به تحلیل یا تغییرِ اطّلاعاتِ ذخیره‌شده در یک فایل نیاز داشته باشید هم کاربردی است. برای مثال می‌توانید برنامه‌ای بنویسید که محتویّات یک فایل متنی را می‌خواند و آن را به شکلی بازنویسی می‌کند که در یک مرورگر قابل نمایش باشد.

اوّلین گام برای کار کردن با اطّلاعاتِ داخلِ یک فایل خواندنِ آن و انتقال آن به حافظه است. می‌توانید کلّ یک فایل را یک‌جا بخوانید یا این که هر بار با یک خطّ آن کار کنید.

خواندن کامل یک فایل

برای آغاز کردن به فایلی نیاز داریم که چند خطّ متن داخلِ آن باشد. بیایید از فایلی شروع کنیم که در خود عدد پی را تا ۳۰ رقم اعشار دارد و هر ده رقم اعشار در یک خط قرار گرفته است:

1
2
3
3.1415926535
  8979323846
  2643383279

برای اجرا کردن مثال‌های پیشِ رو می‌توانید این خط‌ها را در یک ویرایشگر وارد کنید و آن را با نام pi_digits.txt ذخیره کنید یا این فایل را از منابع این کتاب به آدرس https://nostarch.com/pythoncrashcourse2e دریافت کنید. فایل را در همان پوشه‌ای ذخیره کنید که برنامه‌های این فصل را نگه می‌دارید.

این برنامه‌ای است که این فایل را باز می‌کند، آن را می‌خواند و محتویّات آن را روی صفحه چاپ می‌کند:

1
2
3
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 را چاپ می‌کنیم و تمام فایل متنی را دریافت می‌کنیم:

1
2
3
3.1415926535
  8979323846
  2643383279

تنها تفاوت بین خروجی و فایلِ اصلی خطّ خالی اضافه‌ای در آخر خروجی است. این خطّ خالی به این دلیل پدیدار شده که read() هنگام رسیدن به پایان فایل یک رشته‌ی خالی برمی‌گرداند؛ این رشته‌ی خالی به صورت یک خطّ خالی نمایش داده می‌شود. اگر بخواهید این خطّ خالی را حذف کنید می‌توانید هنگام فراخوانی print()، rstrip() را به کار بگیرید:

1
2
3
with open('pi_digits.txt') as file_object:
    contents = file_object.read()
    print(contents.rstrip())

به خاطر بیاورید که متد rstrip() پایتون تمام کاراکترهای فاصله را از سمت راست یک رشته حذف می‌کند. حال خروجی کاملاً با فایل اصلی منطبق است:

1
2
3
3.1415926535
  8979323846
  2643383279

2 - مسیر فایل‌ها

زمانی که نام فایل ساده‌ای مانند 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 قرار دارد می‌توانید برای باز کردن فایلی در داخل آن، از یک مسیر فایل نسبی استفاده کنید. مسیر فایل نسبی به پایتون می‌گوید که محلّ واردشده را نسبت به پوشه‌ای در نظر بگیر که محلِّ ذخیره‌سازی فایلِ برنامه‌ی در حالِ اجرای فعلی است و داخل آن را ببیند. برای مثال می‌توانید به این صورت بنویسید:

1
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() بدهیم:

1
2
3
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"

3 - خواندن خط به خط

اغلب هنگامِ خواندنِ یک فایل نیاز دارید هر خطّ آن را بررسی کنید. شاید به دنبال اطّلاعات خاصّی در فایل باشید یا بخواهید متن داخل آن را به شکلی تغییر دهید. برای مثال ممکن است بخواهید فایلی از داده‌های هواشناسی را بخوانید و با خط‌هایی که در توضیح آب و هوای آن روز عبارت sunny (آفتابی) داشته باشد کاری انجام دهید. در گزارش اخبار ممکن است به دنبال خط‌هایی با برچسب <headline> باشید و به آن خط شکل خاصّی بدهید.

برای بررسی خط به خط یک فایل می‌توانید با حلقه‌ی for در شیء فایل حلقه بزنید:

1
2
3
4
5
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 استفاده می‌کنیم تا به پایتون اجازه دهیم فایل را به طور مناسب باز و بسته کند. برای بررسی محتویّات فایل، در شیء فایل حلقه می‌زنیم و با هر خطّ فایل کار می‌کنیم (۳).

وقتی هر خط را چاپ می‌کنیم، به فاصله‌های خالی بیشتری بین خط‌ها برمی‌خوریم:

1
2
3
4
5
3.1415926535

  8979323846

  2643383279

این خط‌های خالی به این دلیل پدیدار می‌شوند که در فایل متنی در پایان هر خط یک کاراکتر خطّ جدید پنهان وجود دارد. تابع print هم که پس از هر بار فراخوانی یک خطّ جدید اضافه می‌کند، در نتیجه در پایان هر خط در مجموع به دو خطّ خالی برمی‌خوریم: یکی از فایل و یکی هم به خاطر print(). هنگام فراخوانی print() با به‌کارگیری rstrip() برای هر خط این خط‌های خالی اضافی حذف می‌شوند:

1
2
3
4
5
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 در یک لیست ذخیره می‌کند و سپس خارج از آن، خط‌ها را چاپ می‌کند:

1
2
3
4
5
6
7
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 متناظر با یک خطّ فایل است، خروجی عیناً مشابه محتویّات فایل خواهد بود.

4 - کار کردن با محتویّات یک فایل

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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 افزوده و کاراکتر خطّ جدید را از هر خط حذف می‌کند (۲). در ۳ رشته را چاپ می‌کنیم و طول آن را نمایش می‌دهیم:

1
2
3
3.1415926535    8979323846    2643383279

36

متغیّر pi_string شامل فاصله‌هایی است که در سمت چپ رقم‌های هر خط وجود دارد، امّا می‌توانیم با به‌کارگیری strip() به جای rstrip() از شرّ آن‌ها خلاص شویم:

1
2
3
4
5
6
7
--snip--

for line in lines:
    pi_string += line.strip()

print(pi_string)
print(len(pi_string))

حال رشته‌ای داریم که عدد پی را تا ۳۰ رقم اعشار دربرمی‌گیرد. رشته ۳۲ حرف است چون علاوه بر رقم‌های اعشار عدد ۳ و علامت اعشار را هم در خود دارد:

1
2
3
3.141592653589793238462643383279

32
نکته
زمانی که پایتون فایلی متنی را می‌خواند تمام متن آن را به عنوان رشته در نظر می‌گیرد. اگر مقداری عددی را خوانده‌اید و می‌خواهید با آن محاسبات عددی انجام دهید، باید آن را به کمک تابع int() به عدد صحیح یا به کمک تابع float() به عدد اعشاری تبدیل کنید.

فایل‌های بزرگ: یک میلیون رقم اعشار

تا اینجا بر روی تحلیل فایلی متنی تمرکز کرده‌ایم که تنها سه خط را شامل می‌شد امّا کد این مثال‌ها می‌توانند با فایل‌های بسیار بزرگ‌تر هم به خوبی کار کنند. اگر با فایلی متنی شروع کنیم که به جای ۳۰ رقم، عدد پی را تا یک میلیون رقم اعشار داشته باشد، می‌توانیم رشته‌ای بسازیم که تمام این رقم‌ها را دربربگیرد. نیاز به ایجاد هیچ تغییری در برنامه نیست به جز این که نام فایل ورودی را عوض کنیم. ما همچنین فقط ۵۰ رقم اوّل را چاپ می‌کنیم تا نیاز نباشد یک میلیون رقم را در ترمینال ببینیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
filename = 'pi_million_digits.txt'

with open(filename) as file_object:
    lines = file_object.readlines()

pi_string = ''
for line in lines:
    pi_string += line.strip()

print(f"{pi_string[:52]}...")
print(len(pi_string))

خروجی نشان می‌دهد که رشته‌ای از عدد پی تا یک میلیون رقم اعشار داریم:

1
2
3
3.14159265358979323846264338327950288419716939937510...

1000002

پایتون هیچ نوع محدودیّت ذاتی برای حجم داده‌ای که می‌خواهید با آن کار کنید، ندارد؛ می‌توانید با هر چه‌قدر داده که حافظه‌ی سیستم شما می‌تواند در خود جا دهد کار کنید.

نکته
برای اجرا کردن این برنامه (و بسیاری از مثال‌هایی که از این پس می‌آیند) باید منابع موجود در https://nostarch.com/pythoncrashcourse2e را دریافت کنید.

آیا تاریخ تولّد شما در عدد پی وجود دارد؟

من همیشه کنجکاو بوده‌ام که بدانم آیا تاریخ تولّدم در جایی از رقم‌های عدد پی وجود دارد یا نه. بیایید از برنامه‌ای که الان نوشتیم استفاده کنیم تا ببینیم تاریخ تولّد فرد در یک میلیون رقم اعشار اوّل عدد پی وجود دارد یا نه. این کار را می‌توانیم با بیان روز تولّد هر کس به صورت یک رشته و سپس جست‌و‌جوی آن رشته در pi_string انجام دهیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--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 وجود دارد یا نه. آن را امتحان می‌کنیم:

1
2
3
Enter your birthdate, in the form mmddyy: 120372

Your birthday appears in the first million digits of pi!

روز تولّد من در رقم‌های اعشار پی پیدا شد! پس از این که فایلی را خواندید می‌توانید محتویّات آن را به هر شکلی که تصوّر می‌کنیم تحلیل و بررسی کنید.

5 - خودآزمایی

۱۰-۱- یادگیریِ پایتون: فایلی خالی در ویرایشگر متنی خود باز کنید و در چند خط آن‌چه تا اینجا در مورد پایتون آموخته‌اید خلاصه کنید. هر خط را به این شکل آغاز کنید: In Python you can… فایل را با نام learning_python.txt در همان پوشه‌ای ذخیره کنید که تمرین‌های این فصل را در آن قرار می‌دهید. برنامه‌ای بنویسید که فایل را بخواند و آن‌چه را نوشته‌اید سه بار چاپ کند. محتوّیات را یک بار با خواندن کامل فایل، یک بار با حلقه زدن بر شیء فایل و یک بار با ذخیره کردن خط‌های آن در یک لیست و سپس کار کردن با آن بیرون از بخش with، چاپ کنید.

۱۰-۲- یادگیری C: می‌توانید به کمک متد replace() هر کلمه‌ای را در یک رشته با کلمه‌ای دیگر جایگزین کنید. اینجا مثالی سریع درباره‌ی جایگزینی 'dog' با 'cat' در یک جمله می‌بینید:

1
2
3
>>> message = "I really like dogs."
>>> message.replace('dog', 'cat')
'I really like cats.'

فایلی که هم‌اکنون ساخته‌اید، learning_python.txt، خط به خط بخوانید و کلمه‌ی Python را با نام زبان دیگری، مثلاً C، جایگزین کنید. هر خطِ تغییریافته را روی صفحه چاپ کنید.

6 - نوشتن در یک فایل

یکی از ساده‌ترین راه‌ها برای ذخیره‌سازی داده‌ها نوشتن آن‌ها در یک فایل است. زمانی که متنی را در فایلی می‌نویسید، خروجی بعد از بستن ترمینالی که آن را نشان می‌دهد، همچنان در دسترس خواهد بود. می‌توانید خروجی را پس از پایان اجرای یک برنامه بررسی کنید یا آن را در اختیار دیگران قرار دهید. همچنین می‌توانید برنامه‌هایی بنویسید که متن را دوباره به حافظه منتقل کرده و بعداً با آن‌ها کار کند.

نوشتن در یک فایل خالی

برای نوشتن در یک فایل باید open() را با نشان‌وند دومی فراخوانی کنید که به پایتون می‌گوید می‌خواهید در آن فایل چیزی بنویسید. برای این که ببینیم این کار چگونه انجام می‌شود، پیام ساده‌ای می‌نویسیم و به جای نمایش آن بر روی صفحه، آن را در یک فایل ذخیره می‌کنیم:

1
2
3
4
filename = 'programming.txt'

with open(filename, 'w') as file_object:
    file_object.write("I love programming.")

در این مثال فراخوانی open() در ۱ با دو نشان‌وند انجام می‌شود. اوّلین نشان‌وند همچنان نام فایلی95 94 است که می‌خواهیم باز کنیم. دومین نشان‌وند 'w'، به پایتون می‌گوید که می‌خواهیم فایل را در حالت نوشتن باز کنید. شما می‌توانید فایلی را در حالت خواندن ('r')، حالت نوشتن ('w')، حالت الحاق ('a') و یا حالتی که به شما اجازه‌ی خواندن و نوشتن فایل می‌دهد ('r+') باز کنید. اگر نشان‌وند مربوط به حالت خواندن را حذف کنید، پایتون به طور پیش‌فرض فایل را در حالت فقط-خواندنی باز می‌کند.

اگر فایلی که می‌خواهید در آن چیزی بنویسید وجود نداشته باشد، تابع open() آن را به طور خودکار برای شما می‌سازد. با این حال هنگام باز کردن فایل در حالت ('w') دقّت کنید؛ چون اگر قبلاً فایلی به این نام وجود داشته باشد، پایتون پیش از بازگرداندن شیء فایل، تمام محتویّات آن را پاک می‌کند.

در ۲ از متد write() بر روی شیء فایل استفاده می‌کنیم تا رشته‌ای در آن بنویسیم. این برنامه هیچ خروجی‌ای ندارد امّا اگر فایل programming.txt را باز کنید در آن یک خط می‌بینید:

1
I love programming.

این فایل مانند هر فایل دیگری در رایانه‌ی شما رفتار می‌کند. می‌توانید آن را باز کنید، متن جدیدی در آن بنویسید، از آن چیزی کپی کنید، به آن چیزی اضافه کنید و … .

نکته
پایتون تنها می‌تواند در یک فایل متنی رشته‌ها را بنویسد. اگر می‌خواهید داده‌های عددی را در یک فایل متنی ذخیره کنید باید ابتدا آن را به کمک تابع str() به شکل متنی دربیاورید.

نوشتن چند خط

تابع write() هیچ خطّ جدیدی به متن شما اضافه نمی‌کند. بنابراین اگر بدون افزودن کاراکتر خطّ اضافه بیشتر از یک خط بنویسید، ممکن است فایل شما آن‌جور که انتظار دارید به نظر نیاید:

1
2
3
4
5
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() هر رشته را در خط خود جای می‌دهد:

1
2
3
4
5
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")

حال در خروجی خط‌های جداگانه می‌بینیم:

1
2
I love programming.
I love creating new games.

شما می‌توانید از فاصله، کاراکترِ تب یا خط‌های خالی برای شکل دادن به خروجی خود استفاده کنید همانگونه که در خروجی‌های خود در ترمینال انجام می‌دادید.

اضافه کردن به یک فایل

اگر می‌خواهید مطلبی به یک فایل اضافه کنید، به جای رونویسی بر مطالب قبلی می‌توانید فایل را در حالت الحاق باز کنید. زمانی که فایل را در این حالت باز می‌کنید، پایتون پیش از بازگرداندنِ شیء، محتویّات آن را پاک نمی‌کند. هر خطّی که در فایل بنویسید به انتهای آن افزوده خواهد شد. اگر فایل وجود نداشته باشد، پایتون یک فایل خالی برای شما خواهد ساخت.

حال write_message.py را با اضافه کردن چند دلیل برای دوست‌داشتن برنامه‌نویسی به فایلِ موجودِ programming.txt تغییر می‌دهیم:

1
2
3
4
5
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 اضافه می‌شوند:

1
2
3
4
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.

در نهایت به همان محتویّات اصلی فایل می‌رسیم که دو خطّ جدید به آن اضافه شده است.

7 - خودآزمایی

۱۰-۳- مهمان‌ها: برنامه‌ای بنویسید که از کاربران نام آن‌ها را بپرسد. زمانی که پاسخ دادند، نام آن‌ها در فایلی به نام guest.txt بنویسید.

۱۰-۴- کتابچه‌ی مهمان‌ها: یک حلقه‌ی while بنویسید که از کاربران نامشان را بپرسد. زمانی که نام خود را وارد کردند، پیام سلامی خطاب به آن‌ها روی صفحه چاپ کنید و در فایلی به نام guest_book.txt خطی برای ثبت کردنِ بازدیدِ آن‌ها اضافه کنید. مطمئن شوید که در فایل هر عنوان ورودی در یک خط جدید ذخیره می‌شود.

۱۰-۵- نظرسنجی برنامه‌نویسی: یک حلقه‌ی while بنویسید که از افراد بپرسد چرا برنامه‌نویسی را دوست دارند. هر بار که کسی دلیل وارد کرد، آن را به فایلی که تمام پاسخ‌ها را نگه می‌دارد، اضافه کنید.

8 - استثناها

پایتون از شیءی خاص به نام *استثنا * برای مدیریّت خطاهایی که حین اجرای برنامه رخ می‌دهند استفاده می‌کند. هرگاه پس از رخ دادن خطایی، پایتون مطمئن نباشد که چه باید بکند، یک شیء استثنا می‌سازد. اگر کدی برای رسیدگی کردن به استثنا بنویسید برنامه به کار خود ادامه خواهد داد. اگر استثنا را مدیریّت نکنید برنامه متوقّف شده و یک traceback نمایش داده می‌شود که حاوی گزارشی در مورد استثنایی است که رخ داده است.

رسیدگی به استثناها با بلوک‌های try-except انجام می‌پذیرد. بلوک try-except از پایتون می‌خواهد که کاری انجام دهد و امّا در عین حال مشخّص می‌کند که در صورت رخ دادن استثنا حین انجام آن کار چه کاری بکند. زمانی که از بلوک‌های try-except استفاده می‌کنید برنامه‌های شما حتی در صورت به اشتباه افتادن به کار خود ادامه می‌دهند. کاربران به جای traceback که ممکن است خواندن آن برای آن‌ها گیج‌کننده باشد، پیام‌هایی مناسب می‌بینند که شما خود نوشته‌اید.

رسیدگی به استثنای ZeroDivisionError

به خطایی ساده که موجب می‌شود اعلام استثنا کند می‌پردازیم. احتمالاً می‌دانید که تقسیم کردن عددی بر صفر ناممکن است، امّا بگذارید از پایتون بخواهیم این کار را انجام دهد:

1
print(5/0)

البته که پایتون نمی‌تواند چنین کاری کند بنابراین به یک traceback برمی‌خوریم:

1
2
3
4
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 چه شکلی است:

1
2
3
4
try:
    print(5/0)
except ZeroDivisionError:
    print("You can't divide by zero!")

خطّی را که موجب بروز خطا می‌شود، print(5/0)، در بلوک try قرار می‌دهیم. اگر کد داخل try کار کند، پایتون از روی بلوک except می‌پرد. اگر کد داخل بلوک try منجر به خطا شود، پایتون به دنبال بلوک except‌ای می‌گردد که استثنای آن با استثنای رخ‌داده منطبق باشد و کد داخل آن را اجرا می‌کند.

در این مثال کد داخل try خطای ZeroDivisionError را تولید می‌کند بنابراین پایتون به دنبال بلوک except‌ای می‌گردد که به او بگوید چه باید بکند. پایتون سپس کد داخل آن بلوک را اجرا می‌کند و کاربر به جای traceback یک پیام خطای دوستانه می‌بیند:

1
You can't divide by zero!

اگر پس از بلوک try-except کد دیگری نوشته شده باشد، اجرای برنامه ادامه می‌یابد زیرا به پایتون گفته‌ایم که چگونه خطا را مدیریّت کند.

9 - به‌کارگیری استثناها برای پیش‌گیری از از کار افتادن برنامه

مدیریّت خطاها به ویژه زمانی مهم است که برنامه پس از رخ دادن خطا کارِ زیادی برای انجام دادن داشته باشد. این وضع اغلب در برنامه‌هایی رخ می‌دهد که از کاربر ورودی می‌گیرند. اگر برنامه به ورودی‌های نامناسب به درستی پاسخ دهد، به جای از کار افتادن می‌تواند به ورودی‌های درستِ بیشتری رسیدگی کند.

بیایید ماشین حساب ساده‌ای بسازیم که تنها عمل تقسیم را انجام می‌دهد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
print("Give me two numbers, and I'll divide them.")
print("Enter 'q' to quit.")

while True:
    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 به دست بیاید (۳). این برنامه هیچ کاری برای مدیریّت خطا انجام نمی‌دهد بنابراین درخواست تقسیم بر صفر موجب از کار افتادن برنامه می‌شود:

1
2
3
4
5
6
7
8
9
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 قرار می‌گیرد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--snip--

while True:
    --snip--

    if second_number == 'q':
        break

    try:
        answer = int(first_number) / int(second_number)
    except ZeroDivisionError:
        print("You can't divide by 0!")
    else:
        print(answer)

از پایتون می‌خواهیم که تلاش خود را برای به انجام رساندن عملیّات تقسیم در یک بلوک try بکند (۱). این بلوک تنها شامل کدی است که ممکن است موجب بروز خطا شود. هر کدی که به اجرای موفقیّت‌آمیز بلوک try وابسته باشد به بلوک else افزوده می‌شود. در این مورد اگر عملیّات تقسیم موفّق باشد از بلوک else برای چاپ کردن نتیجه در ۳ استفاده می‌کنیم.

بلوک except به پایتون می‌گوید که هنگام رخ دادن ZeroDivisionError چه پاسخی بدهد (۲). اگر بلوک try به دلیل خطای تقسیم بر صفر با موفقیّت انجام نشد، پیامی دوستانه چاپ می‌کنیم که به کاربر بگوید چگونه از این خطا پرهیز کند. اجرای برنامه ادامه می‌یابد و کاربر هرگز traceback‌ای نمی‌بیند:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
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 - رسیدگی به استثنای FileNotFoundError

یکی از مشکلات شایع هنگام کار کردن با فایل‌ها مدیریّت فایل‌های ناموجود است. فایلی که به دنبال آن هستید ممکن است در جای متفاوتی باشد، نام آن ممکن است اشتباه نوشته شده باشد یا چه بسا اصلاً وجود نداشته باشد. شما می‌توانید به کمک یک بلوک try-except و با روشی سرراست به تمام این وضعیّت‌ها رسیدگی کنید.

حال بیایید تلاش کنیم فایلی را که وجود ندارد بخوانیم. برنامه‌ی زیر می‌خواهد محتویّات آلیس در سرزمین عجایب را بخواند امّا من فایل alice.txt را در پوشه‌ی alice.py ذخیره نکرده‌ام:

1
2
3
4
filename = 'alice.txt'

with open(filename, encoding='utf-8') as f:
    contents = f.read()

اینجا دو تغییر وجود دارد. یکی به‌کارگیری متغیّر f برای نمایش شیء فایل است که قراردادی مرسوم است. دیگری استفاده از نشان‌وند encoding است. این نشان‌وند هنگامی مورد نیاز است که کدگذاری پیش‌فرض سیست 97 م شما با کدگذاری فایلی که می‌خواهید بخوانید هم‌خوانی ندارد.

پایتون نمی‌تواند فایلی را که وجود ندارد بخواند پس استثنایی ایجاد می‌کند:

1
2
3
4
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() است:

1
2
3
4
5
6
7
filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    print(f"Sorry, the file {filename} does not exist.")

در این مثال کد داخل بلوک try ایجاد FileNotFoundError می‌کند پس پایتون به دنبال بلوک except‌ای می‌گردد که به این خطا بخورد. سپس کد داخل آن بلوک را اجرا می‌کند که به جای یک traceback به صدور پیام دوستانه‌ای منجر می‌شود:

1
Sorry, the file alice.txt does not exist.

این برنامه در صورت پیدا نشدن فایل کاری برای انجام دادن ندارد بنابراین رسیدگی به خطا چیز زیادی به آن اضافه نمی‌کند. اجازه دهید این مثال را ادامه دهیم و ببینیم مدیریّت استثناها هنگام کار کردن با بیش از یک فایل چه کمکی می‌توانند بکنند.

تحلیل متن

شما می‌توانید فایل‌های متنی شامل کتاب‌های کامل را98 تحلیل کنید. بخش زیادی از ادبیّات کلاسیک در قالب فایل‌های متنی ساده در دسترس هستند چون در قلمرو عمومی هستند. متن‌های به کار رفته در این بخش از پروژه‌ی گوتنبرگ گرفته شده‌اند (http://gutenberg.org) پروژه‌ی گوتنبرگ مجموعه‌ای از کارهای ادبی را نگه‌داری می‌کند که در قلمرو عمومی در دسترس‌اند و اگر در پروژه‌های برنامه‌نویسی خود به کار با متن‌های ادبی علاقه‌مندید، منبع بسیار خوبی خواهد بود.

اجازه دهید متن آلیس در سرزمین عجایب را بیرون بکشیم تا تعداد کلمات متن آن را بشمریم. از متد رشته‌ای split() استفاده می‌کنیم که می‌تواند رشته را به لیستی از کلمات تبدیل کند. اینجا می‌بینیم که split() با رشته‌ای که شامل عنوان متن یعنی "Alice in Wonderland" است چه می‌کند:

1
2
3
>>> title = "Alice in Wonderland"
>>> title.split()
['Alice', 'in', 'Wonderland']

متد split() هر جا در رشته به فاصله‌ی خالی می‌رسد، آن بخش را جدا کرده و تمام تکّه‌ها را در لیستی ذخیره می‌کند. نتیجه لیستی از کلماتِ رشته است، هرچند ممکن است در برخی از کلمات علائم سجاوندی هم پیدا شود. برای شمارش کلمات آلیس در سرزمین عجایب از split() بر روی کل متن استفاده می‌کنیم. سپس تنها اعضای لیست را می‌شماریم تا به عددی سرانگشتی از تعداد کلمات موجود در متن برسیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
filename = 'alice.txt'

try:
    with open(filename, encoding='utf-8') as f:
        contents = f.read()
except FileNotFoundError:
    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 چندتاست:

1
The file alice.txt has about 29465 words.

این تعداد کمی از مقدار واقعی بیشتر است زیرا ناشر اطّلاعات بیشتری در فایل متنی به‌کاررفته ارائه کرده است با این حال تقریب خوبی از تعداد کلمات آلیس در سرزمین عجایب است.

11 - کار کردن با چند فایل

می‌خواهیم کتاب‌های بیشتری برای تحلیل اضافه کنیم. امّا پیش از این کار اجازه دهید بدنه‌ی این برنامه را به داخل تابعی به نام count_words() منتقل کنیم. با این کار تحلیل کردن چند کتاب آسان‌تر خواهد بود:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def count_words(filename):
    """Count the approximate number of words in a file."""
    try:
        with open(filename, encoding='utf-8') as f:
            contents = f.read()
    except FileNotFoundError:
        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 بیرون گذاشته‌ام تا ببینم برنامه چگونه فقدان فایل را مدیریّت می‌کند:

1
2
3
4
5
6
7
def count_words(filename):
    --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']

for filename in filenames:
    count_words(filename)

فقدانِ فایل siddhartha.txt تاثیری بر اجرای باقی برنامه ندارد:

1
2
3
4
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 به پایتون می‌گوید که هیچ کاری انجام ندهد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def count_words(filename):
    """Count the approximate number of words in a file."""
    try:
        --snip--
    except FileNotFoundError:
        pass
    else:
        --snip--

filenames = ['alice.txt', 'siddhartha.txt', 'moby_dick.txt', 'little_women.txt']

for filename in filenames:
    count_words(filename)

تنها تفاوت این برنامه با قبلی عبارت pass در ۱ است. حال وقتی FileNotFoundError رخ می‌دهد کد داخل بلوک except اجرا می‌شود امّا هیچ اتّفاقی نمی‌افتد. هیچ traceback‌ای و هیچ خروجی‌ای در پاسخ به خطای رخ‌داده تولید نمی‌شود. کاربران تعداد کلمات هر فایل موجود را می‌بینند امّا در مورد وجود نداشتنِ یک فایل هیچ علامتی نمی‌بینند:

1
2
3
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 بنویسید. کاربران ما این فایل را نمی‌بینند ولی ما قادر هستیم که آن را بخوانیم و در مورد متن‌های پیدانشده کاری بکنیم.

کدام خطا را گزارش کنیم؟

چگونه بفهمیم که کی باید خطا را به کاربر گزارش کنیم و چه زمانی باید از کنار آن به آرامی بگذریم؟ اگر کاربران بدانند که چه متن‌هایی قرار است تحلیل شوند، ممکن است از دیدنِ پیامی که می‌گوید چرا برخی از متن‌ها تحلیل نشده‌اند استقبال کنند. اگر کاربران منتظرِ دیدن برخی نتایج هستند و نمی‌دانند چه کتاب‌هایی قرار است تحلیل شود، شاید نیازی نباشد که از نبودنِ برخی از متن‌ها مطّلع شوند. دادنِ اطّلاعات غیرمنتظره به کاربران می‌تواند باعث کاهش کارایی برنامه‌ی شما شود. ساختارهای مدیریّت خطای پایتون به شما امکان می‌دهد که در زمان بروز خطا اطّلاعاتی را که در اختیار کاربر قرار می‌دهید با جزئیات بسیاری کنترل کنید؛ این که چه‌قدر اطّلاعات با کاربر به اشتراک گذاشته شود به شما بستگی دارد.

برنامه‌ای که به خوبی نوشته و به درستی آزموده شده خیلی در معرضِ خطاهای داخلی مانند خطاهای نحوی یا منطقی نیست. امّا هر بار برنامه‌ی شما به چیزی خارج از خود وابسته باشد، مانند ورودیِ کاربر، وجودِ یک فایل یا ارتباط شبکه، امکان رخ دادنِ یک استثنا وجود دارد. کمی تجربه به شما کمک می‌کند که بدانید کی از بلوک‌های مدیریّت استثنا استفاده کنید و به کاربر در مورد خطاهایی که رخ می‌دهد چه‌قدر اطّلاعات بدهید.

12 - خودآزمایی

۱۰-۶- جمع کردن: یکی از مشکلات رایج هنگام دریافت مقادیر عددی زمانی رخ می‌دهد که افراد به جای عدد متن وارد می‌کنند. وقتی تلاش می‌کنیم که این ورودی را با int به عدد تبدیل کنیم خطای ValueError دریافت می‌کنید. برنامه‌ای بنویسید که از کاربر دو عدد بگیرد. آن‌ها را جمع و نتیجه را چاپ کند. اگر هر کدام از ورودی‌ها عدد نبودند خطای ValueError را بگیرید و پیامی دوستانه چاپ کنید. برنامه‌ی خود را با وارد کردن دو عدد و سپس متنی به جای یکی از اعداد بیازمایید.

۱۰-۷- ماشین جمع کردن: کد تمرین ۱۰-۶ را در یک حلقه‌ی while قرار دهید تا حتّی اگر کاربر یک بار به اشتباه به جای عدد متن وارد کرد، همچنان بتواند عددهای بعدی را وارد کند.

۱۰-۸- سگ‌ها و گربه‌ها: دو فایل به نام cats.txt و dogs.txt بسازید. در اوّلی دستِ کم نام سه گربه و در دیگری نام سه سگ را ذخیره کنید. برنامه‌ای بنویسید که سعی کند این فایل‌ها را بخواند و محتویّات آن‌ها را روی صفحه چاپ کند. کد خود را در یک بلوک try-except قرار دهید تا خطای FileNotFoundError را بگیرد و در صورت فقدان فایلی پیامی چاپ کند. یکی از فایل‌ها را به مکان دیگری در سیستم خود منتقل کنید و مطمئن شوید که بلوک except به درستی اجرا می‌شود.

۱۰-۹- سگ‌ها و گربه‌های بی‌صدا: بلوک except را در تمرین ۱۰-۸ جوری تغییر دهید که در صورت یافت نشدنِ بدون سر و صدا آن را رد کند.

۱۰-۱۰- کلمات مشترک: پروژه‌ی گوتنبرگ (https://gutenberg.org) را ببینید و چند متن پیدا کنید که دوست دارید آن‌ها را تحلیل کنید. فایل‌های متنی آن‌ها را دانلود کنید یا متن خام را از مرورگر خود کپی و در یک فایل متنی در رایانه‌ی خود ذخیره کنید.

می‌توانید از متد count() استفاده کنید تا بفهمید یک کلمه یا عبارت چند بار در یک رشته تکرار شده است. برای مثال کد زیر تعداد دفعات پدیدار شدن 'row' را در یک رشته می‌شمارد:

1
2
3
4
5
>>> line = "Row, row, row your boat"
>>> line.count('row')
2
>>> line.lower().count('row')
3

توجّه کنید که تبدیل تمام رشته به حروف کوچک به کمک lower() تمام موارد وقوع یک کلمه را صرف نظر از شکل و بزرگ و کوچکی حروف آن پیدا می‌کند.

برنامه‌ای بنویسید که فایل‌هایی را که در پروژه‌ی گوتنبرگ پیدا کرده‌اید بخواند و تعیین کند که کلمه‌ی 'the' چند بار در هر کدام تکرار شده است. این عدد تقریبی خواهند بود زیرا کلمه‌هایی مانند 'then' و 'there' هم شمرده خواهند شد. حال شمردن 'the ' را امتحان کنید، با افزودنِ یک فاصله در انتهای رشته، و ببینید که تعداد نسبت به قبل چه‌قدر کمتر می‌شود.

13 - ذخیره‌سازی داده‌ها

بسیاری از برنامه‌های شما از کاربران می‌خواهند که انواع خاصّی اطّلاعات را وارد کنند. ممکن است به کاربران امکان دهید که تنظیمات پیش‌فرض خود را در یک بازی ذخیره یا داده‌هایی را برای بصری‌سازی ارائه کنند. تمرکزِ برنامه‌ی شما بر هر چیزی باشد، شما اطّلاعاتی را که کاربر ارائه می‌کند در ساختمان‌های داده‌ای مانند لیست‌ها و دیکشنری‌ها ذخیره می‌کنید. زمانی که کاربران برنامه‌ای را می‌بندند تقریباً همیشه می‌خواهید که اطّلاعات وارد شده را ذخیره کنید. یک راه ساده برای این کار ذخیره کردن داده به کمک ماژول json است.

ماژول json به شما امکان می‌دهد که ساختمان‌های داده‌ی ساده‌ی پایتون را درون یک فایل بریزید و دفعه‌ی بعد که برنامه اجرا شد آن‌ها را دوباره بارگذاری کنید. همچنین می‌توانید از json برای به اشتراک گذاشتن اطّلاعات بین برنامه‌های مختلف پایتون استفاده کنید. و حتّی بهتر از این، شکل داده‌ی JSON منحصر به پایتون نیست بنابراین می‌توانید داده‌هایی را که به صورت JSON ذخیره کرده‌اید با افرادی که با بسیاری دیگری از زبان‌های برنامه‌نویسی کار می‌کنند به اشتراک بگذارید.

نکته
JSON در ابتدا برای جاوا‌اسکریپت توسعه داده شد. با این حال مدت‌هاست که به عنوان شکل فایلی مشترک بین زبان‌های بسیاری از جمله پایتون تبدیل شده است.

استفاده از json.dump() و json.load()

بیایید برنامه‌ای برای ذخیره کردن مجموعه‌ای عدد و برنامه‌ای دیگر برای خواندن دوباره‌ی آن عدد بنویسیم. برنامه‌ی اوّل از json.dump() برای ذخیره کردن مجموعه‌ی اعداد و برنامه‌ی دوم از json.load() استفاده می‌کند.

تابع json.dump() دو نشان‌وند می‌گیرد: قطعه‌ای داده و شیء فایلی که برای ذخیره کردن آن استفاده می‌شود. اینجا چگونگی استفاده از json.dump() را برای ذخیره کردن یک لیست اعداد می‌بینید:

1
2
3
4
5
6
7
8
import json

numbers = [2, 3, 5, 7, 11, 13]

filename = 'numbers.json'

with open(filename, 'w') as f:
    json.dump(numbers, f)

ابتدا ماژول json را وارد می‌کنیم و لیستی از اعداد می‌سازیم. در ۱ نام فایلی را که در آن لیست اعداد ذخیره می‌شود انتخاب می‌کنیم. برای نشان دادن این که داده‌ها در فایل به شکل JSON ذخیره می‌شوند، مرسوم است که از پسوند .json برای آن استفاده کنیم. سپس در ۲ فایل را در حالت نوشتن باز می‌کنیم تا به json اجازه بدهیم داده‌ها را در فایل بنویسد. در ۳ از تابع json.dump() برای ذخیره کردن لیست numbers در فایل numbers.json استفاده می‌کنیم.

این برنامه هیچ خروجی‌ای ندارد امّا اجازه دهید فایل numbers.json را باز کنیم و نگاهی به آن بیندازیم. داده به همان شکلی ذخیره شده که در پایتون دیده می‌شود:

1
[2, 3, 5, 7, 11, 13]

حال برنامه‌ای می‌نویسیم که از json.load() استفاده کرده، لیست را دوباره خوانده و به حافظه منتقل می‌کند:

1
2
3
4
5
6
7
8
import json

filename = 'numbers.json'

with open(filename) as f:
    numbers = json.load(f)

print(numbers)

در ۱ مطمئن می‌شویم که اطّلاعات را از همان فایلی می‌خوانیم که در آن ذخیره کرده‌ایم. این بار فایل را در حالت خواندن باز می‌کنیم چون پایتون تنها نیاز دارد که فایل را بخواند (۲). در ۳ از تابع json.load() برای بارگذاری اطّلاعات ذخیره‌شده در numbers.json استفاده می‌کنیم و آن را به متغیّر numbers نسبت می‌دهیم. در نهایت لیست اعداد بازیابی‌شده را چاپ می‌کنیم و می‌بینیم که همان لیست ساخته‌شده در number_writer.py است:

1
[2, 3, 5, 7, 11, 13]

این راهی ساده برای به اشتراک گذاشتن داده‌ها بین دو برنامه است.

14 - ذخیره کردن و خواندن داده‌هایی که کاربر تولید کرده

هنگام کار با داده‌هایی که کاربر تولید کرده، ذخیره کردن آن‌ها با JSON سودمند است، زیرا اگر اطّلاعات او را جایی به شکلی ذخیره نکنید، با توقّف اجرای برنامه آن‌ها را از دست خواهید داد. به مثالی نگاه می‌کنیم که در آن در اوّلین اجرای برنامه از کاربر نام او را می‌پرسیم و سپس زمانی که برنامه را دوباره اجرا کرد، نامش را به خاطر می‌آوریم.

با ذخیره کردن نام کاربر شروع می‌کنیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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() استفاده می‌کنیم و نام کاربری و یک شیء فایل را به آن می‌دهیم تا نام کاربری را در یک فایل ذخیره کند (۲). سپس پیامی چاپ می‌کنیم که ذخیره شدنِ اطّلاعات را به کاربران اطّلاع می‌دهد (۳):

1
2
3
What is your name? Eric

We'll remember you when you come back, Eric!

اکنون برنامه‌ای جدید می‌نویسیم که به کاربری که نام او قبلاً ذخیره شده سلام می‌کند:

1
2
3
4
5
6
7
8
import json

filename = 'username.json'

with open(filename) as f:
    username = json.load(f)

print(f"Welcome back, {username}!")

در ۱ از json.load() برای خواندنِ اطّلاعات ذخیره شده در username.json استفاده و آن را به متغیّر username نسبت می‌دهیم. اکنون نام‌های کاربری را بازیابی کرده‌ایم و می‌توانیم به آن‌ها خوش‌آمد بگوییم (۲):

1
Welcome back, Eric!

ما به ترکیب این دو برنامه در یک فایل نیاز داریم. زمانی که کسی remember_me.py را اجرا می‌کند، می‌خواهیم در صورتِ امکان، نامِ کاربریِ او را از حافظه بازیابی کنیم؛ بنابراین با یک بلوک try شروع می‌کنیم که سعی می‌کند نام کاربری را بازیابی کند. اگر فایل username.json وجود نداشته باشد، در بلوک except نام کاربری را می‌پرسیم و برای دفعات بعد در username.json ذخیره می‌کنیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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)
except FileNotFoundError:
    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() برای ذخیره کردن نام کاربری استفاده می‌کنیم و پیامی چاپ می‌کنیم (۵).

هر کدام از بلوک‌ها که اجرا شوند، نتیجه یک نام کاربری و یک پیام مناسب خواهد بود. اگر اوّلین اجرای برنامه باشد، خروجی این‌چنین خواهد بود:

1
2
3
What is your name? Eric

We'll remember you when you come back, Eric!

در غیر این صورت:

1
Welcome back, Eric!

این خروجی‌ای است که اگر برنامه قبلاً یک بار اجرا شده باشد خواهید دید.

15 - بازآرایی

اغلب شما به جایی می‌رسید که کدتان کار می‌کند امّا101 تشخیص می‌دهید که با شکستن کد به مجموعه‌ای از تابع‌ها با وظیفه‌ی ویژه، می‌توانید آن را بهبود دهید. این فرآیند را بازآرایی می‌نامند. بازآرایی کد شما را تمیز‌تر و خواندن و توسعه‌ی آن را ساده‌تر می‌کند.

ما می‌توانیم remember_me.py را با انتقال منطق آن به یک یا چند تابع بازآرایی کنیم. تمرکز remember_me.py بر تبریک گفتن به کاربر است بنابراین تمام کد موجود را به تابعی به نام greet_user() منتقل می‌کنیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import json

def greet_user():
    """Greet the user by name."""
    filename = 'username.json'

    try:
        with open(filename) as f:
            username = json.load(f)
    except FileNotFoundError:
        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() را بازآرایی کنیم تا این همه کار مختلف را انجام ندهد. کار را با انتقال کدی که نام کاربری ذخیره‌شده را بازیابی می‌کند شروع می‌کنیم و آن را به یک تابع جدا منتقل می‌کنیم:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import json

def get_stored_username():
    """Get stored username if available."""
    filename = 'username.json'

    try:
        with open(filename) as f:
            username = json.load(f)
    except FileNotFoundError:
        return None
    else:
        return username

def greet_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() بیرون بکشیم. اگر نام کاربری وجود نداشته باشد، باید کدی که تقاضای نام کاربری جدید می‌کند به تابعی مخصوصِ این کار انتقال یابد:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import json

def get_stored_username():
    """Get stored username if available."""
    --snip--

def get_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

def greet_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() را در صورت لزوم صدا می‌کند که این تابع آخر فقط مسئول گرفتن نام کاربری جدید و ذخیره کردنِ آن است. بخش بخش کردنِ کار، قسمتِ مهمّی از نوشتن کدی شفّاف است که بتوان آن را به سادگی نگهداری کرد و توسعه داد.

16 - خودآزمایی

۱۰-۱۱- عدد دلخواه: برنامه‌ای بنویسید که عدد دلخواهِ کاربر را بپرسد. از json.dump() برای ذخیره کردن این عدد در یک فایل استفاده کنید. برنامه‌ای جدا بنویسید که این مقدار را بخواند و پیامی مانند این چاپ کند: "I know your favorite number! It's ____"

۱۰-۱۲- عددِ دلخواهِ به‌خاطرسپرده‌شده: دو برنامه‌ی تمرین ۱۰-۱۱ را در یک فایل ترکیب کنید. اگر عدد قبلاً وجود داشت، آن را به کاربر گزارش کنید. اگر نه، از کاربر عدد دلخواهش را بپرسید و در یک فایل ذخیره کنید. برنامه را دوبار اجرا کنید تا مطمئن شوید کار می‌کند.

۱۰-۱۳- تاییدِ کاربر: آخرین نسخه‌ی remember_me.py فرض می‌کند که یا کاربر قبلاً نام کاربری خود را وارد کرده یا اوّلین بار است که برنامه اجرا می‌شود. در صورتی‌که کاربر فعلی آخرین کسی نباشد که از برنامه استفاده کرده، باید برنامه را تغییر دهیم.

پیش از چاپ کردن پیام خوش‌آمدگویی در greet_user()، از کاربر بپرسید که آیا این نام کاربری درست است یا نه. اگر نبود get_new_username() را دوباره فراخوانی کنید تا نام کاربری تصحیح شود.

17 - خلاصه

در این فصل کار کردن با فایل‌ها را آموختید. شما خواندن یک‌باره‌ی یک فایل و خواندنِ خط به خط محتویّات فایل را یاد گرفتید. دیدید که چگونه می‌توان در فایلی نوشت یا متنی به انتهای آن افزود. درباره‌ی استثناها و نحوه‌ی مدیریّت استثناهایی که ممکن است در برنامه رخ دهد، خواندید. سرانجام آموختید که چگونه ساختمان‌های داده‌ی پایتون را ذخیره کنید تا اطّلاعاتی که کاربران ارائه می‌کنند حفظ شود و نیاز نباشد که در هر اجرای برنامه یک بار آن‌ها را از اوّل وارد کنند.

در فصل ۱۱ روش‌های موثّری برای آزمودن کد خود می‌آموزید که کمک می‌کند به کد خود به عنوان کدی که درست توسعه داده شده، اعتماد کنید و همچنین هنگامی که به توسعه‌ی برنامه‌هایی می‌پردازید که قبلاً نوشته‌اید شما را در شناسایی ایراد آن‌ها یاری می‌کند.