فایل و JSON
داده را روی دیسک بخوان و بنویس: open با with و حالتهای r و w و a، encoding=utf-8، خواندن خط به خط، تحلیل یک لاگ واقعی، pathlib برای مسیرها، فایل CSV، و JSON با json.dumps و json.load و ensure_ascii=False؛ همراه خطاهای FileNotFoundError و PermissionError.
- حدود ۲۱ دقیقه مطالعه
- ۸ مثال اجراشده
- ۳ تمرین با جواب
- مقدماتی تا متوسط
- بهروزرسانی: ۲۹ مهر ۱۴۰۵
توی این درس یاد میگیری برنامهات داده را نگه دارد: در فایل بنویسد و بعداً بخواند. با open و دستور with فایل را باز میکنی، فرق حالتهای r (خواندن)، w (نوشتن از نو) و a (اضافه به آخر) را میفهمی و یاد میگیری چرا برای فارسی باید encoding="utf-8" بدهی. یک لاگ وبسرور واقعی را خط به خط تحلیل میکنی، با pathlib مسیرها را تمیز میسازی، و دو قالب پرکاربرد داده را یاد میگیری: CSV (جدول) و JSON (همان زبانی که APIها و فایلهای تنظیمات با آن حرف میزنند).
مسئله: همهچیز با بسته شدن برنامه پاک میشود
Section titled “مسئله: همهچیز با بسته شدن برنامه پاک میشود”هر چه تا اینجا در متغیرها گذاشتی، در حافظه (RAM) بود؛ برنامه که تمام شود، همه میرود. برنامهی واقعی باید داده را ماندگار کند (روی دیسک) و دادهای را که برنامههای دیگر ساختهاند بخواند: لاگها، فایلهای تنظیمات، خروجی یک API، گزارش اکسل.
تشبیه: دفترچهای که باید باز و بسته شود
Section titled “تشبیه: دفترچهای که باید باز و بسته شود”کار با فایل مثل کار با یک دفترچه است: باز میکنی (open)، میخوانی یا مینویسی، و میبندی. اگر دفترچه را باز رها کنی، ممکن است آخرین نوشتههایت ثبت نشود. with مثل دستیاری است که بعد از کارت همیشه دفترچه را میبندد، حتی اگر وسط کار حادثهای (خطا) پیش بیاید. حالت هم مثل این است که بگویی «فقط میخوانم» (r)، «پاکش کن و از نو بنویس» (w) یا «به صفحهی آخر اضافه کن» (a).
مثالهای عملی
Section titled “مثالهای عملی”همه را در ~/py اجرا کن.
مثال ۱: نوشتن، اضافه کردن، خواندن
Section titled “مثال ۱: نوشتن، اضافه کردن، خواندن”with open("notes.txt", "w", encoding="utf-8") as f: f.write("خط اول\n") f.write("خط دوم\n")with open("notes.txt", "a", encoding="utf-8") as f: f.write("خط سوم (اضافهشده)\n")with open("notes.txt", encoding="utf-8") as f: content = f.read()print(content)print(repr(content))خط اولخط دومخط سوم (اضافهشده)
'خط اول\nخط دوم\nخط سوم (اضافهشده)\n'open(اسم, حالت, encoding=...)فایل را باز میکند وas fیک اسم کوتاه به آن میدهد."w"فایل را از نو میسازد (اگر بود، محتوایش پاک میشود)."a"به آخرش اضافه میکند. بدون حالت، پیشفرض"r"(فقط خواندن) است.writeبرخلافprintخودش به خط بعد نمیرود؛\nرا خودت بگذار.read()کل فایل را یک متن برمیگرداند.reprنشان میدهد واقعاً داخلش چیست:\nهمان خط جدید است وهمان نیمفاصلهی فارسی (درس متن و encoding در دورهی مبانی).
دستورهای ترمینال (cat notes.txt) هم همین فایل را میبینند؛ فایلی که پایتون مینویسد یک فایل متنی معمولی است.
مثال ۲: خواندن خط به خط
Section titled “مثال ۲: خواندن خط به خط”برای فایلهای بزرگ (یک لاگ چندگیگابایتی) read() کل فایل را در حافظه میریزد. بهتر است خط به خط بخوانی؛ for روی فایل همین کار را میکند:
with open("notes.txt", encoding="utf-8") as f: for i, line in enumerate(f, start=1): print(i, line.rstrip("\n"))1 خط اول2 خط دوم3 خط سوم (اضافهشده)هر line شامل \n آخرش است؛ rstrip("\n") آن را برمیدارد تا print دو خط جدید چاپ نکند. (strip() بدون آرگومان فاصلههای اول و آخر را هم برمیدارد.)
مثال ۳: تحلیل یک لاگ واقعی
Section titled “مثال ۳: تحلیل یک لاگ واقعی”یک فایل access.log با شکل لاگ nginx بساز (با nano، یا اگر nginx داری، از /var/log/nginx/access.log کپی کن):
10.0.0.1 - - [21/Oct/2026:10:00:01 +0000] "GET / HTTP/1.1" 200 51210.0.0.2 - - [21/Oct/2026:10:00:02 +0000] "GET /login HTTP/1.1" 200 102410.0.0.1 - - [21/Oct/2026:10:00:03 +0000] "GET /admin HTTP/1.1" 403 12810.0.0.3 - - [21/Oct/2026:10:00:04 +0000] "GET /old HTTP/1.1" 404 6410.0.0.1 - - [21/Oct/2026:10:00:05 +0000] "POST /login HTTP/1.1" 500 3210.0.0.2 - - [21/Oct/2026:10:00:06 +0000] "GET /missing HTTP/1.1" 404 64counts = {}ips = set()with open("access.log", encoding="utf-8") as f: for line in f: parts = line.split() ip, code = parts[0], parts[8] ips.add(ip) counts[code] = counts.get(code, 0) + 1print("کدها:", counts)print("IPهای متفاوت:", len(ips))کدها: {'200': 2, '403': 1, '404': 2, '500': 1}IPهای متفاوت: 3split() هر خط را با فاصله میشکند؛ IP کلمهی اول ([0]) و کد وضعیت کلمهی نهم ([8]) است (بشمار!). بعد همان الگوهای درس لیست و دیکشنری: شمارش با get و حذف تکراری با set. این همان کاری است که در دورهی لینوکس با awk '{print $9}' | sort | uniq -c میکردی.
open("data.txt", "w") روی فایلی که از قبل هست چه میکند؟
جواب درست: محتوایش را پاک میکند و از نو مینویسد
"w" همیشه از صفر شروع میکند؛ برای اضافه کردن "a".
مثال ۴: مسیرها با pathlib
Section titled “مثال ۴: مسیرها با pathlib”برای کار با مسیرها (وجود داشتن، اسم، پسوند، ساختن پوشه) ماژول pathlib تمیزترین راه است:
from pathlib import Pathp = Path("notes.txt")print(p.exists(), p.name, p.suffix, p.stat().st_size)Path("out").mkdir(exist_ok=True)Path("out/hello.txt").write_text("سلام\n", encoding="utf-8")print(Path("out/hello.txt").read_text(encoding="utf-8").strip())for f in sorted(Path(".").glob("*.py")): print(f)print(Path("/var/log") / "nginx" / "access.log")True notes.txt .txt 58سلامlines.pylogstat.pynotes.pypaths.py/var/log/nginx/access.log| کد | کار |
|---|---|
p.exists() |
آیا وجود دارد؟ |
p.name / p.suffix / p.parent |
اسم / پسوند / پوشهی بالایی |
p.stat().st_size |
اندازه به بایت (۵۸: حرفهای فارسی هر کدام دو بایتاند) |
Path("x").mkdir(exist_ok=True) |
ساختن پوشه (mkdir -p) |
p.read_text() / p.write_text(s) |
خواندن / نوشتن کل فایل در یک خط |
Path(".").glob("*.py") |
پیدا کردن فایلها با الگو (مثل ls *.py) |
Path("/var/log") / "nginx" |
چسباندن مسیرها با / (بدون نگرانی از اسلش اضافه) |
from pathlib import Path یعنی «از ماژول pathlib فقط Path را بیاور» (درس بعد). write_text و read_text خودشان فایل را باز و بسته میکنند، پس برای فایلهای کوچک لازم نیست with بنویسی.
مثال ۵: فایلهای تنظیمات سیستم
Section titled “مثال ۵: فایلهای تنظیمات سیستم”پایتون هر فایل متنی را میخواند؛ مثلاً همان /etc/os-release درس اول لینوکس:
info = {}with open("/etc/os-release") as f: for line in f: line = line.strip() if "=" in line: key, value = line.split("=", 1) info[key] = value.strip('"')print(info["PRETTY_NAME"], "|", info["VERSION_CODENAME"])Ubuntu 24.04.5 LTS | noblesplit("=", 1) فقط در اولین = میشکند (اگر مقدار خودش = داشت، خراب نمیشود)، و strip('"') کوتیشنهای دور مقدار را برمیدارد. حالا کل فایل در یک دیکشنری است.
مثال ۶: CSV، جدولها
Section titled “مثال ۶: CSV، جدولها”CSV (مقدارهای جداشده با ویرگول) سادهترین قالب جدول است و اکسل هم بازش میکند:
import csvrows = [["name", "price", "qty"], ["کتاب", "120000", "2"], ["خودکار", "15000", "5"]]with open("cart.csv", "w", newline="", encoding="utf-8") as f: csv.writer(f).writerows(rows)total = 0with open("cart.csv", newline="", encoding="utf-8") as f: for row in csv.DictReader(f): total += int(row["price"]) * int(row["qty"])print(f"جمع: {total:,}")جمع: 315,000cat cart.csvname,price,qtyکتاب,120000,2خودکار,15000,5csv.DictReader خط اول را سرستون میگیرد و هر سطر را یک دیکشنری با همان کلیدها میدهد (row["price"]). همهی مقدارها متناند، پس int() لازم است. چرا ماژول csv و نه split(",")؟ چون اگر یک مقدار خودش ویرگول داشته باشد (مثلاً "تهران، خیابان آزادی")، csv آن را درست داخل کوتیشن میگذارد و درست میخواند. newline="" را ماژول csv توصیه میکند تا خطها روی همهی سیستمها درست باشند.
مثال ۷: JSON، زبان مشترک برنامهها
Section titled “مثال ۷: JSON، زبان مشترک برنامهها”JSON شکل متنی همان لیستها و دیکشنریهای پایتون است، و تقریباً هر API و ابزاری (از داکر تا GitHub) با آن حرف میزند:
import jsonserver = {"name": "web1", "ip": "10.0.0.1", "ports": [80, 443], "active": True, "owner": "علی"}text = json.dumps(server)print(text)print(json.dumps(server, ensure_ascii=False, indent=2))back = json.loads(text)print(back["ports"][1], type(back)){"name": "web1", "ip": "10.0.0.1", "ports": [80, 443], "active": true, "owner": "\u0639\u0644\u06cc"}{ "name": "web1", "ip": "10.0.0.1", "ports": [ 80, 443 ], "active": true, "owner": "علی"}443 <class 'dict'>json.dumps(dump string) دادهی پایتون را به متن JSON تبدیل میکند؛json.loadsبرعکس.- دقت کن
Trueپایتون در JSON میشودtrue(کوچک)، وNoneمیشودnull. - پیشفرض، حرفهای غیرانگلیسی به شکل
\u0639...نوشته میشوند (درست است ولی ناخوانا).ensure_ascii=Falseفارسی را همانطور نگه میدارد وindent=2خروجی را مرتب و خوانا میکند.
مثال ۸: ذخیره و بازیابی JSON در فایل
Section titled “مثال ۸: ذخیره و بازیابی JSON در فایل”import jsonservers = [{"name": "web1", "disk": 72}, {"name": "db1", "disk": 91}]with open("servers.json", "w", encoding="utf-8") as f: json.dump(servers, f, ensure_ascii=False, indent=2)with open("servers.json", encoding="utf-8") as f: data = json.load(f)for s in data: print(s["name"], s["disk"])web1 72db1 91cat servers.json[ { "name": "web1", "disk": 72 }, { "name": "db1", "disk": 91 }]json.dump و json.load (بدون s) مستقیم با فایل کار میکنند. این سادهترین «دیتابیس» است: برنامه وضعیتش را در یک فایل JSON ذخیره میکند و دفعهی بعد از همانجا ادامه میدهد. (برای دادهی بزرگ یا چندکاربره، دورهی SQL.)
پشت پرده: بافر و «بسته نشدن» فایل
Section titled “پشت پرده: بافر و «بسته نشدن» فایل”وقتی write میکنی، پایتون داده را فوراً روی دیسک نمینویسد؛ اول در یک بافر (حافظهی موقت) جمع میکند و وقتی بافر پر شد یا فایل بسته شد، یکجا مینویسد، چون نوشتن روی دیسک کند است. اگر فایل را نبندی و برنامه ناگهان بمیرد، ممکن است آخرین نوشتهها هیچوقت به دیسک نرسند. برای همین with مهم است: با تمام شدن بلوک، حتی اگر خطایی رخ داده باشد، فایل بسته و بافر خالی میشود.
و encoding چه میکند؟ فایل روی دیسک فقط بایت است (درس بیت و بایت). encoding="utf-8" به پایتون میگوید این بایتها را با قاعدهی UTF-8 به حرف تبدیل کند؛ اگر قاعدهی اشتباهی بدهی، همان بایتها حرفهای عجیب (یا خطا) میشوند. اندازهی ۵۸ بایتی notes.txt هم از همینجاست: حرفهای فارسی در UTF-8 دو بایتاند و نیمفاصله سه بایت.
جدولهای مرجع
Section titled “جدولهای مرجع”| حالت | معنی | اگر فایل نباشد | اگر باشد |
|---|---|---|---|
"r" (پیشفرض) |
خواندن | FileNotFoundError |
از اول میخواند |
"w" |
نوشتن | میسازد | پاک میکند و از نو |
"a" |
اضافه | میسازد | به آخرش اضافه میکند |
"x" |
ساختن انحصاری | میسازد | FileExistsError (برای جلوگیری از پاک شدن) |
| کار | کد |
|---|---|
| کل فایل به متن | f.read() یا Path(p).read_text() |
| خط به خط | for line in f: |
| نوشتن | f.write("متن\n") |
| JSON به داده | json.load(f) / json.loads(s) |
| داده به JSON | json.dump(d, f, ensure_ascii=False, indent=2) / json.dumps(d) |
| CSV | csv.reader(f)، csv.DictReader(f)، csv.writer(f) |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) فایلی که نیست
Section titled “۱) فایلی که نیست”with open("nofile.txt") as f: print(f.read())Traceback (most recent call last): File "/home/ali/py/nf.py", line 1, in <module> with open("nofile.txt") as f: ^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'nofile.txt'همان «No such file or directory» دورهی لینوکس. اسم نسبی (nofile.txt) نسبت به پوشهای که برنامه را از آن اجرا کردهای حساب میشود، نه پوشهی فایل .py. راهحل: با pwd ببین کجایی، یا مسیر کامل بده. (در درس بعد یاد میگیری این خطا را بگیری.)
۲) اجازه نداشتن
Section titled “۲) اجازه نداشتن”PermissionError: [Errno 13] Permission denied: '/etc/shadow'برنامهی پایتون با کاربر تو اجرا میشود و همان دسترسیهای دورهی لینوکس را دارد. راهحل: فقط فایلهایی را بخوان که اجازه داری؛ برای فایلهای سیستمی، یک کپی بگیر یا (فقط اگر واقعاً لازم است) sudo python3.
۳) "w" بهجای "a"
Section titled “۳) "w" بهجای "a"”یک لاگ را با "w" باز کنی، هر اجرا لاگ قبلی را پاک میکند. راهحل: برای «اضافه کن» همیشه "a".
۴) نوشتن عدد مستقیم
Section titled “۴) نوشتن عدد مستقیم”with open("notes.txt", "w", encoding="utf-8") as f: f.write(42)TypeError: write() argument must be str, not intراهحل: f.write(str(42)) یا f.write(f"{n}\n").
۵) JSON با کوتیشن تکی
Section titled “۵) JSON با کوتیشن تکی”import jsonjson.loads("{'name': 'web1'}")json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 2 (char 1)JSON فقط کوتیشن دوتایی (") را قبول میکند؛ {'name': 'web1'} شکل چاپ دیکشنری پایتون است، نه JSON. راهحل: JSON را با json.dumps بساز، نه دستی؛ و برای بررسی یک فایل JSON در ترمینال: python3 -m json.tool file.json.
برنامهای بنویس که سه خط دربارهی خودت در about.txt بنویسد، بعد فایل را بخواند و تعداد خطها و تعداد کل حرفها را چاپ کند.
دیدن جواب
with open("about.txt", "w", encoding="utf-8") as f: f.write("اسمم علی است.\n") f.write("در تهران زندگی میکنم.\n") f.write("پایتون یاد میگیرم.\n")with open("about.txt", encoding="utf-8") as f: lines = f.readlines()print("تعداد خط:", len(lines))print("تعداد حرف:", sum(len(line) for line in lines))تعداد خط: 3تعداد حرف: 57readlines() همهی خطها را در یک لیست میدهد. تعداد حرفها شامل \nهای آخر خط و نیمفاصلهها هم هست.
تمرین اصلی: از همان access.log مثال ۳، IPهایی را پیدا کن که دستکم یک خطای ۴xx یا ۵xx (کد ۴۰۰ و بالاتر) داشتهاند، و برای هر IP تعداد خطاهایش را در errors.json (با فارسی خوانا و indent=2) ذخیره کن. بعد فایل را بخوان و چاپ کن.
دیدن جواب
import jsonerrors = {}with open("access.log", encoding="utf-8") as f: for line in f: parts = line.split() ip, code = parts[0], int(parts[8]) if code >= 400: errors[ip] = errors.get(ip, 0) + 1with open("errors.json", "w", encoding="utf-8") as f: json.dump(errors, f, ensure_ascii=False, indent=2)with open("errors.json", encoding="utf-8") as f: print(f.read()){ "10.0.0.1": 2, "10.0.0.3": 1, "10.0.0.2": 1}int(parts[8]) کد را عدد میکند تا بشود با >= 400 مقایسهاش کرد. 10.0.0.1 یک ۴۰۳ و یک ۵۰۰ داشت.
یک «دفترچهی کار» ماندگار بساز: برنامهی todo.py که اگر todo.json نبود، با یک لیست خالی شروع کند، یک کار جدید (از input) به آن اضافه کند، فهرست را با شماره چاپ کند و دوباره در فایل ذخیره کند. دو بار اجرا کن و ثابت کن کار اول بعد از بسته شدن برنامه نگه داشته شده.
دیدن جواب
import jsonfrom pathlib import Path
path = Path("todo.json")if path.exists(): tasks = json.loads(path.read_text(encoding="utf-8"))else: tasks = []
tasks.append(input("کار جدید: "))for i, t in enumerate(tasks, start=1): print(f"{i}. {t}")path.write_text(json.dumps(tasks, ensure_ascii=False, indent=2), encoding="utf-8")کار جدید: خرید نان1. خرید نانکار جدید: تمرین پایتون1. خرید نان2. تمرین پایتوندر اجرای دوم «خرید نان» از فایل خوانده شد. این الگو (بخوان، تغییر بده، ذخیره کن) پایهی هر برنامهای است که باید چیزی را به خاطر بسپارد.
آزمونک
Section titled “آزمونک”چرا with open(...) as f بهتر از open تنهاست؟
جواب درست: فایل را بعد از بلوک، حتی با بروز خطا، خودکار میبندد
بسته شدن یعنی بافر روی دیسک نوشته میشود.
برای اضافه کردن یک خط به آخر لاگ، کدام حالت؟
جواب درست: "a"
"w" محتوای قبلی را پاک میکند.
json.dumps(d, ensure_ascii=False) چه فایدهای دارد؟
جواب درست: حرفهای فارسی را خوانا نگه میدارد بهجای \uXXXX
بدون آن، فارسی به شکل کدهای \u نوشته میشود.
True پایتون در JSON چه میشود؟
جواب درست: true
None هم null میشود.
مقدارهایی که csv.DictReader میدهد چه نوعیاند؟
جواب درست: همیشه متن (str)
برای حساب، int() یا float() لازم است.
FileNotFoundError با اینکه فایل کنار اسکریپت است. علت محتمل؟
جواب درست: برنامه از پوشهی دیگری اجرا شده و مسیر نسبی نسبت به آن پوشه است
مسیر نسبی از پوشهی فعلی (pwd) حساب میشود.
جمعبندی
Section titled “جمعبندی”with open(اسم, حالت, encoding="utf-8") as f:و بلوک تورفته؛withفایل را همیشه میبندد.- حالتها:
"r"خواندن،"w"از نو (پاک میکند!)،"a"اضافه. f.read()کل فایل،for line in f:خط به خط (برای فایلهای بزرگ)،f.write("...\n")نوشتن متن.pathlib.Pathبرای مسیرها:exists،mkdir(exist_ok=True)،glob،read_text،write_text، و/برای چسباندن.- CSV با ماژول
csv(مقدارها متناند)؛ JSON باjson.dump/loadوensure_ascii=False, indent=2. - خطاها:
FileNotFoundError(مسیر)،PermissionError(دسترسی)،JSONDecodeError(JSON نامعتبر).
| دستور | کاری که میکند |
|---|---|
with open("f.txt", encoding="utf-8") as f: | باز کردن برای خواندن |
open("f.txt", "w", encoding="utf-8") | نوشتن از نو |
open("f.txt", "a", encoding="utf-8") | اضافه به آخر |
for line in f: line.rstrip() | خط به خط |
Path("x").exists() / .mkdir(exist_ok=True) | وجود / ساختن پوشه |
json.load(f) / json.dump(d, f, ensure_ascii=False, indent=2) | خواندن / نوشتن JSON |
json.loads(s) / json.dumps(d) | JSON از متن / به متن |
csv.DictReader(f) | خواندن CSV با سرستون |
python3 -m json.tool f.json | بررسی و مرتبکردن JSON در ترمینال |