فایلها و مدیریت خطا
دستور with در پایتون (Context Manager)، خواندن خط به خط و ساخت فایل CSV
در درس قبل دیدیم فراموش کردن close() چه دردسرهایی دارد. پایتون راهحلی دارد که بستن فایل را خودکار میکند.
دستور with
with open("test.txt", "w") as f:
f.write("salam\n")
f.write("bye")
print("good bye")
with open(...) as f:فایل را باز میکند و دستگیرهاش را درfمیگذارد.- همهٔ کار با فایل داخل بلوک تورفته انجام میشود.
- به محض اینکه تورفتگی تمام شود، پایتون فایل را خودش میبندد؛ حتی اگر وسط بلوک خطایی رخ داده باشد.
پس print("good bye") وقتی اجرا میشود که فایل از قبل بسته و ذخیره شده است. هم کد کوتاهتر است، هم دیگر نمیشود بستن را فراموش کرد.
💡 به چیزهایی که با
withکار میکنند مدیر زمینه (context manager) میگویند. فایل فقط یک نمونه است؛ اتصال پایگاه داده، قفلها و … هم همینطورند. قاعده: برای فایل همیشه ازwithاستفاده کنید.
حالت افزودن هم همینطور است:
with open("test.txt", "a") as f:
f.write("\nnew_line")
with open("test.txt") as f:
print(f.read())
خواندن: سه روش
اول یک فایل نمونه بسازیم:
with open("names.csv", "w") as f:
f.write("name,age\nsara,20\nali,12\ngholi,80\nparsa,3\nreza,33\n")
print("ساخته شد")
(مثالهای بعدی این صفحه از همین فایل استفاده میکنند؛ اگر خطای FileNotFoundError گرفتید، اول این بلوک را اجرا کنید.)
۱. کل فایل یکجا: read()
with open("names.csv", "r") as f:
content = f.read()
print(content)
read() کل فایل را در یک رشته برمیگرداند. برای فایلهای کوچک عالی است؛ ولی یک فایل ۱۰ گیگابایتی را یکجا در حافظه نریزید!
۲. خط به خط: for
with open("names.csv", "r") as f:
for line in f:
print(line, end="")
پایتون در هر دور فقط یک خط را میخواند، پس فایلهای خیلی بزرگ هم مشکلی ندارند.
یادتان هست مشکل خط خالی اضافه چه بود؟ هر line یک \n در انتها دارد و print یک \n دیگر هم اضافه میکند. دو راهحل:
print(line, end=""): بهprintبگوییم خودش خط جدید اضافه نکند.print(line.strip()):\n(و فاصلههای اضافهٔ دو طرف) را از خود خط حذف کنیم.
with open("names.csv", "r") as f:
for line in f:
print(line.strip())
راه دوم معمولاً بهتر است، چون بعداً میخواهید با خود خط کار کنید و آن \n مزاحم است.
۳. فهرست خطوط: readlines()
with open("names.csv") as f:
lines = f.readlines()
print(lines)
فایل CSV
CSV (مخفف Comma-Separated Values) سادهترین قالب ذخیرهٔ دادههای جدولی است: یک فایل متنی معمولی که هر خطش یک سطر جدول است و ستونها با کاما از هم جدا شدهاند:
name,age
sara,20
ali,12
اکسل، گوگلشیت و کتابخانههایی مثل Pandas مستقیم بازش میکنند. این فایلها نوع داده ندارند، همهچیزشان متن است؛ ولی اکسل وقتی ببیند ستونی فقط عدد دارد، خودش آن را عدد در نظر میگیرد.
مسئله: ساخت CSV از ورودی کاربر
برنامهای بنویسید که نام و سن ۵ نفر را بگیرد و یک فایل CSV بسازد که ستون اول نام و ستون دوم سن باشد.
نسخهٔ ۱:
handler = open("people.csv", "w")
handler.write("name,age\n") # سطر عنوان (header)
for _ in range(5):
name = input("Enter a name: ")
age = input("Enter an age: ")
handler.write(name + ",")
handler.write(age + "\n")
handler.close()
print(open("people.csv").read())
💡
_اسم متغیری است که برنامهنویسهای پایتون وقتی به مقدار شمارنده نیازی ندارند به کار میبرند. یعنی «فقط ۵ بار تکرار کن».
نسخهٔ ۲: تمیزتر با with و f-string
with open("people.csv", "w", encoding="utf-8") as f:
f.write("name,age\n")
for i in range(1, 6):
print(f"Person #{i}:")
name = input(" name: ")
age = input(" age: ")
f.write(f"{name},{age}\n")
with open("people.csv", encoding="utf-8") as f:
print(f.read())
با f-string کل سطر در یک write نوشته میشود. range(1, 6) هم برای این است که شمارهٔ نفرات از ۱ شروع شود، نه ۰.
خواندن و پردازش CSV
برای جدا کردن ستونها از متد split استفاده میکنیم:
with open("names.csv") as f:
header = f.readline() # سطر اول (عنوانها) را جدا بخوان
total_age = 0
count = 0
for line in f:
name, age = line.strip().split(",")
print(f"{name:>8} → {age} ساله")
total_age += int(age)
count += 1
print("میانگین سن:", total_age / count)
"sara,20".split(",") رشته را در کاماها میشکند و ["sara", "20"] برمیگرداند که با باز کردن چندتایی در دو متغیر میریزیم.
⚠️ اگر خود داده کاما داشته باشد (مثلاً آدرس
"Tehran, Iran")، این روش ساده خراب میشود. برای CSVهای واقعی از ماژول استانداردcsvیا کتابخانهٔ Pandas استفاده کنید که این حالتها را درست مدیریت میکنند.
نوشتن چندخطی
با رشتهٔ سهکوتیشنی میشود چند خط را یکجا نوشت:
with open("notes.txt", "w") as f:
f.write("""line one
line two
line three
""")
with open("notes.txt") as f:
print(f.read())
تمرین: ماژول name_utils
دو تابع بنویسید:
write_names(file_name, number_of_names): به تعدادnumber_of_namesاسم از کاربر بگیرد و در فایلfile_nameبنویسد (هر اسم در یک خط).read_names(file_name): همهٔ اسامی فایل را به ترتیب (هر اسم در یک خط) نمایش دهد.
در آخر این دو تابع را در یک ماژول به اسم name_utils.py بگذارید و از کد اصلی استفاده کنید.
جواب
def write_names(file_name, number_of_names):
with open(file_name, "w", encoding="utf-8") as f:
for i in range(number_of_names):
name = input("Enter a name: ")
f.write(name + "\n")
def read_names(file_name):
with open(file_name, "r", encoding="utf-8") as f:
for name in f:
print(name, end="")
write_names("a.txt", 2)
read_names("a.txt")
بعد این دو تابع را در فایل name_utils.py بگذارید و در کد اصلی:
from name_utils import write_names, read_names
write_names("b.txt", 2)
read_names("b.txt")