فایلها و سیستمعامل
تا اینجا هر برنامهای که نوشتیم، با بسته شدن همه چیزش را فراموش میکرد. با فایل، دادههای برنامه ماندگار میشوند. این اولین قدم به سمت برنامههای واقعی است.
۱. باز کردن و بستن فایل
# open() gives us a "file object". The default mode is 'r' (read).
# The path is relative to the folder the program is started from.
f = open('a.txt', encoding='utf-8')
# An absolute path works too:
# f = open('D:\\myprojects\\a.txt', encoding='utf-8')
content = f.read() # read() returns the WHOLE file as one string
print(content)
f.close() # the file must be closed - if we forget, data can be lost
شکل ابتدایی: نمونه 01_read_whole_file.py
شرح مثال
openیک شیء فایل میدهد که با آن میشود خواند یا نوشت.- مسیر نسبی، نسبت به پوشهای است که برنامه از آن اجرا شده، نه پوشهای که فایل برنامه در آن است. این تفاوت باعث سردرگمی زیادی میشود.
- در مسیر ویندوز باید بکاسلشها را دوتایی بنویسید یا از رشته خام
r'...'استفاده کنید. به خاطر دارید از فصل سوم. - بستن فایل اجباری است. اگر ننویسید، ممکن است دادهای که نوشتهاید روی دیسک ذخیره نشود.
۲. دستور with
فراموش کردن close آنقدر رایج است که پایتون راه بهتری دارد.
از این به بعد همیشه از این شکل استفاده کنید.
# 'with' closes the file automatically, even if an error happens inside.
# This is the only form you should use.
with open('a.txt', encoding='utf-8') as f:
content = f.read()
print(content)
# Here the file is already closed.
print('closed?', f.closed)
دستور with: نمونه 02_with_statement.py
فایل به طور خودکار بسته میشود، حتی اگر وسط بلوک خطایی رخ دهد یا
return اجرا شود. با close دستی، یک خطا
کافی است تا فایل باز بماند. در فصل بیست و چهارم میبینید که
with فقط برای فایل نیست و میشود برای هر چیزی که «باز
و بسته» میشود ساختش، مثل اتصال به پایگاه داده.
۳. روشهای خواندن
# read(n) reads at most n characters instead of the whole file.
with open('a.txt', encoding='utf-8') as f:
print(f.read(5)) # 'Hello'
print(f.read(3)) # the reading continues where it stopped
خواندن چند کاراکتر: نمونه 03_read_n_chars.py
# readline() reads ONE line, including the '\n' at its end.
with open('a.txt', encoding='utf-8') as f:
print(f.readline())
print(f.readline())
# That is why the output looks double spaced: the line already ends with '\n'
# and print() adds another one. end='' fixes it.
print('--- without the extra blank line ---')
with open('a.txt', encoding='utf-8') as f:
print(f.readline(), end='')
print(f.readline(), end='')
خواندن خط به خط: نمونه 04_readline.py
| روش | برمیگرداند | مناسب |
|---|---|---|
read() | کل فایل، یک رشته | فایل کوچک |
read(n) | حداکثر n کاراکتر | خواندن تکهتکه |
readline() | یک خط، همراه \n | وقتی ساختار فایل مشخص است |
readlines() | لیستی از همه خطها | فایل کوچک |
for line in f | خط به خط | بهترین روش، هر اندازه فایل |
نکته مهم: فایل یک مکاننما دارد. هر بار که میخوانید،
مکاننما جلو میرود. برای همین f.read(3) دوم، از جایی که
اولی رها کرده ادامه میدهد.
۴. پیمایش خط به خط
# Looping directly over the file gives its lines one by one.
# This is the best way: it never loads the whole file into memory.
with open('numbers.txt', encoding='utf-8') as file:
for line in file:
print(line, end='')
print()
# readlines() puts every line into a list (only for small files).
with open('numbers.txt', encoding='utf-8') as file:
lines = file.readlines()
print(lines)
print('number of lines:', len(lines))
پیمایش فایل: نمونه 05_loop_over_lines.py
پیمایش مستقیم بهترین روش است، چون کل فایل را در حافظه نمیریزد. برای
فایلی با یک گیگابایت حجم، read() برنامه را از کار
میاندازد ولی حلقه بیمشکل کار میکند.
۵. مشکل کاراکتر خط جدید
# Every line read from a file ends with '\n'. strip() removes it
# (together with the spaces at both ends).
with open('numbers.txt', encoding='utf-8') as file:
for line in file:
clean = line.strip()
if clean == '': # skip the empty lines
continue
number = int(clean)
print(number, number * 2)
پاک کردن خط جدید: نمونه 06_strip_lines.py
هر خطی که از فایل میخوانید، کاراکتر \n در انتهایش
دارد. پس 'ali\n' با 'ali'
برابر نیست و مقایسهتان همیشه نادرست میشود. راه
حل، همیشه strip() است. این دقیقاً یکی از باگهایی بود
که در فایلهای اصلی این دوره وجود داشت.
رد کردن خطهای خالی هم لازم است: بیشتر فایلهای متنی با یک خط خالی تمام میشوند و int('') خطا ایجاد میکند.
۶. نوشتن در فایل
# Mode 'w' creates the file, and ERASES it completely when it already exists.
with open('h.txt', 'w', encoding='utf-8') as f:
f.write('It deletes the existing file!\n')
f.write('Second line.\n') # write() does NOT add '\n' by itself
# writelines() writes a list of strings (still without adding '\n').
lines = ['Ali\n', 'Sara\n', 'Reza\n']
with open('names.txt', 'w', encoding='utf-8') as f:
f.writelines(lines)
نوشتن: نمونه 07_write_file.py
باز کردن فایل با حالت 'w' محتوای قبلیاش را
کاملاً پاک میکند، بدون هیچ سؤال و هشداری. اگر
اشتباهی نام فایل مهمی را بنویسید، از دستش دادهاید. پیش از هر
open(..., 'w') یک بار نام فایل را دوباره بخوانید.
دو نکته دیگر: write برخلاف print خودش خط جدید
اضافه نمیکند، پس اگر \n ننویسید همه چیز پشت سر هم روی یک
خط میچسبد. و writelines هم با وجود نامش، بین خطها چیزی
اضافه نمیکند.
۷. افزودن به انتهای فایل
# Mode 'a' adds to the END of the file and never erases anything.
# Run this program several times and watch the file grow.
with open('g.txt', 'a', encoding='utf-8') as f:
f.write('This is a text file!\n')
with open('g.txt', encoding='utf-8') as f:
print(f.read())
افزودن: نمونه 08_append_file.py
| حالت | اگر فایل نباشد | اگر باشد | کاربرد |
|---|---|---|---|
'r' | خطا | میخواند | خواندن. پیشفرض |
'w' | میسازد | پاک میکند | نوشتن از نو |
'a' | میسازد | به آخرش اضافه میکند | گزارش و لاگ |
'x' | میسازد | خطا | ساخت امن |
۸. حالت ساخت امن
# Mode 'x' creates the file, but raises FileExistsError when it already exists.
# It is the safe way of saying "I do not want to overwrite anything".
try:
with open('i.txt', 'x', encoding='utf-8') as f:
f.write('Created for the first time.\n')
print('The file was created.')
except FileExistsError:
print('The file already exists - nothing was touched.')
حالت x: نمونه 09_create_only.py
حالت 'x' راه صریح گفتن «نمیخواهم چیزی را خراب کنم» است.
ساختار try اینجا آمده تا خطا برنامه را از کار نیندازد؛
فصل بعد کاملاً درباره همین است.
۹. انکدینگ و متن فارسی
# Text files store bytes; the ENCODING says how those bytes become characters.
# Without encoding='utf-8', Persian text can be unreadable on Windows.
persian = 'سلام، به موسسه کادوس خوش آمدید'
with open('persian.txt', 'w', encoding='utf-8') as f:
f.write(persian + '\n')
with open('persian.txt', encoding='utf-8') as f:
print(f.read())
# Always pass encoding='utf-8' when you open a text file, for reading
# and for writing.
انکدینگ: نمونه 10_encoding.py
همیشه encoding='utf-8' بنویسید. اگر
ننویسید، پایتون از انکدینگ پیشفرض سیستم استفاده میکند که در
ویندوز فارسی معمولاً UTF-8 نیست. نتیجه، متن بههمریخته یا خطای
UnicodeDecodeError است. بدتر اینکه برنامه روی
کامپیوتر شما کار میکند و روی کامپیوتر دیگری خراب میشود. در همه
کدهای اصلاحشده این دوره، این پارامتر اضافه شده است.
۱۰. ماژول os
import os
# Does a file exist?
print(os.path.exists('a.txt'))
# Delete a file - but only after checking, otherwise FileNotFoundError.
if os.path.exists('to_delete.txt'):
os.remove('to_delete.txt')
else:
print('to_delete.txt does not exist')
# Where am I?
print('current folder:', os.getcwd())
# What is in this folder?
print(os.listdir('.'))
# Building a path that works on Windows, Linux and macOS.
path = os.path.join('data', 'students', 'list.txt')
print(path)
# Splitting a path.
print(os.path.basename(path)) # list.txt
print(os.path.dirname(path)) # data/students
print(os.path.splitext('app.py')) # ('app', '.py')
# Creating and removing folders.
os.makedirs('demo_folder', exist_ok=True) # exist_ok avoids an error
print(os.path.isdir('demo_folder'))
os.rmdir('demo_folder') # only works on an EMPTY folder
# Size of a file, in bytes.
print(os.path.getsize('a.txt'))
ماژول os: نمونه 11_os_module.py
شرح مثال
os.path.existsمهمترینشان است. پیش از باز کردن هر فایلی که مطمئن نیستید هست، بررسیاش کنید.os.path.joinمسیر میسازد و خودش میداند در ویندوز باید بکاسلش بگذارد و در لینوکس اسلش. هرگز مسیر را با+نچسبانید.makedirs(..., exist_ok=True)پوشه را میسازد و اگر از قبل باشد ساکت میماند. بدون آن پارامتر، خطا میگیرید.rmdirفقط پوشه خالی را پاک میکند. برای پوشه پر باید ازshutil.rmtreeاستفاده کنید، که خطرناک است و باید با احتیاط به کار برود.
۱۱. ماژول pathlib
pathlib جانشین مدرن بیشتر کارهای os.path است.
به جای رشته، با یک شیء مسیر کار میکنید که خودش همه
کارها را بلد است.
# pathlib is the modern replacement for most of os.path.
# A Path object knows how to do almost everything by itself.
from pathlib import Path
path = Path('a.txt')
print(path.exists())
print(path.name) # a.txt
print(path.stem) # a
print(path.suffix) # .txt
print(path.resolve()) # the absolute path
# Reading and writing in one line.
content = path.read_text(encoding='utf-8')
print(content)
Path('hello.txt').write_text('Written with pathlib\n', encoding='utf-8')
# '/' builds a path - much nicer than os.path.join.
data_folder = Path('demo_data')
data_folder.mkdir(exist_ok=True)
(data_folder / 'note.txt').write_text('a note\n', encoding='utf-8')
# Listing the .txt files of the current folder.
for txt_file in Path('.').glob('*.txt'):
print(txt_file.name, txt_file.stat().st_size, 'bytes')
ماژول pathlib: نمونه 12_pathlib.py
| با os | با pathlib |
|---|---|
os.path.exists(p) | Path(p).exists() |
os.path.join(a, b) | Path(a) / b |
os.path.basename(p) | Path(p).name |
os.makedirs(p, exist_ok=True) | Path(p).mkdir(exist_ok=True) |
باز کردن و خواندن با with | Path(p).read_text(encoding='utf-8') |
استفاده از عملگر تقسیم برای ساختن مسیر در نگاه اول عجیب است ولی خیلی
زود به آن عادت میکنید. تابع glob('*.txt') هم همه فایلهای
با آن الگو را پیدا میکند.
۱۲. برنامه دفترچه یادداشت
برنامه کاملی که همه چیز این فصل و فصلهای قبل را کنار هم میگذارد: منو، حلقه، تابع، لیست و فایل.
'''A complete little program: a note book stored in a text file.'''
import os
NOTES_FILE = 'notes.txt'
def load_notes() -> list[str]:
'Return the notes stored in the file (an empty list when there is none).'
if not os.path.exists(NOTES_FILE):
return []
with open(NOTES_FILE, encoding='utf-8') as f:
return [line.strip() for line in f if line.strip() != '']
def save_notes(notes: list[str]) -> None:
'Write the whole list back to the file.'
with open(NOTES_FILE, 'w', encoding='utf-8') as f:
for note in notes:
f.write(note + '\n')
def show_notes(notes: list[str]) -> None:
'Print the notes with their number.'
if len(notes) == 0:
print('(no note yet)')
return
for index, note in enumerate(notes, start=1):
print(f'{index}. {note}')
def main() -> None:
notes = load_notes()
while True:
print('\n1) Show the notes')
print('2) Add a note')
print('3) Delete a note')
print('0) Exit')
choice = input('Your choice: ')
if choice == '1':
show_notes(notes)
elif choice == '2':
text = input('The note: ')
if text.strip() != '':
notes.append(text.strip())
save_notes(notes)
print('Saved.')
elif choice == '3':
show_notes(notes)
number = input('Number of the note to delete: ')
if number.isdigit() and 1 <= int(number) <= len(notes):
removed = notes.pop(int(number) - 1)
save_notes(notes)
print('Deleted:', removed)
else:
print('Wrong number.')
elif choice == '0':
print('Goodbye!')
break
else:
print('Unknown option.')
if __name__ == '__main__':
main()
دفترچه یادداشت: نمونه 17_notes_app.py
شرح مثال
- ساختار برنامه سه لایه است: بارگذاری از فایل، کار روی لیست در حافظه، و ذخیره دوباره. این الگو در برنامههای بزرگ هم همین است.
- بعد از هر تغییر بلافاصله ذخیره میشود، پس اگر برنامه ناگهان بسته شود چیزی از دست نمیرود.
- اعتبارسنجی شماره یادداشت با
isdigit()و بررسی بازه انجام شده است. بدون آن، تایپ یک حرف برنامه را خراب میکرد. - تابع
enumerate(notes, start=1)هم شماره و هم مقدار را میدهد و شمارش را از یک شروع میکند، چون کاربر از یک میشمارد. در فصل بیست و پنجم کاملش را میبینید. - آن خط داخل
load_notesیک درک لیستی است که همstripمیکند و هم خطهای خالی را میاندازد. باز هم فصل بیست و پنجم.
پوشه S18-files-and-os شامل نمونههای
01_read_whole_file.py تا 17_notes_app.py،
بهعلاوه فایلهای داده a.txt و
numbers.txt و friends.txt.
خلاصه فصل
| ابزار | وظیفه آن |
|---|---|
with open(...) as f: | تنها شکل درست باز کردن فایل |
'r' 'w' 'a' 'x' | خواندن، نوشتن از نو، افزودن، ساخت امن |
encoding='utf-8' | همیشه بنویسیدش، مخصوصاً برای فارسی |
for line in f | بهترین راه خواندن، بدون پر کردن حافظه |
line.strip() | پاک کردن \n انتهای هر خط |
f.write(text + '\n') | نوشتن. خودش خط جدید اضافه نمیکند |
os.path.exists | بررسی وجود پیش از باز کردن |
os.path.join | ساختن مسیر مستقل از سیستمعامل |
Path(p) / 'x' | همان کار، به شکل مدرن |
Path(p).read_text() | خواندن کل فایل در یک خط |
تمرینهای فصل هجده
- برنامهای بنویسید که محتوای یک فایل را در فایل دیگری کپی کند.
- بشمارید یک فایل متنی چند خط، چند کلمه و چند کاراکتر دارد.
numbers.txtرا بخوانید و فایل تازهای بسازید که فقط اعداد مثبتش را دارد.- برنامهای بنویسید که هر بار اجرا شود، یک خط با تاریخ و ساعت به فایل گزارش اضافه کند.
- فهرستی از نامها را از فایل بخوانید و الفبایی مرتب چاپشان کنید.
- برنامهای بنویسید که نام یک پوشه را بگیرد و همه فایلهای
py.داخلش را چاپ کند. - یک مدیر رمز ساده بنویسید که رمزها را به شکل
site:passwordدر هر خط فایل نگه دارد. - برنامه دفترچه یادداشت را طوری کامل کنید که بتواند داخل یادداشتها جستوجو هم بکند.