7. فایلهای متنی

7.1. اسکریپت [fic_01]: خواندن/نوشتن یک فایل متنی
اسکریپت زیر مثالی از کار با فایلهای متنی را نشان میدهد:
# وارداتها
import sys
# ایجاد و پردازش متوالی بعدی یک فایل متنی
# این شامل مجموعهای از خطوط در قالب login:pwd:uid:gid:infos:dir:shell است
# هر خط در یک دیکشنری به شکل login => uid:gid:infos:dir:shell ذخیره میشود
# --------------------------------------------------------------------------
def affiche_infos(dico: dict, clé: str):
# مقدار مرتبط با کلید را در دیکشنری «dico» نمایش میدهد، در صورت وجود
if clé in dico.keys():
# مقدار مرتبط با کلید نمایش داده میشود
print(f"{clé} : {dico[clé]}")
else:
#کلید در دیکشنری «dico» یافت نمیشود
print(f"la clé [{clé}] n'existe pas")
# main -----------------------------------------------
#نام فایل تنظیم شده است
FILE_NAME = "./data/infos.txt"
# ایجاد و پر کردن فایل متنی
fic = None
try:
# باز کردن فایل برای نوشتن (w=write)
fic = open(FILE_NAME, "w")
#تولید محتوای دلخواه
for i in range(1, 101):
# یک خط
ligne = f"login{i}:pwd{i}:uid{i}:gid{i}:infos{i}:dir{i}:shell{i}"
#به فایل متنی نوشته میشود
fic.write(f"{ligne}\n")
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# اگر فایل باز شده باشد، بسته میشود
if fic:
fic.close()
# فایل برای خواندن باز میشود
fic = None
try:
# فایل برای خواندن باز میشود
fic = open(FILE_NAME, "r")
# واژهنامه در ابتدا خالی است
dico = {}
#هر خط به دیکشنری [dico] در قالب login => uid:gid:infos:dir:shell اضافه میشود
# خط اول را میخواند، با حذف فاصلههای ابتدایی و انتهایی
ligne = fic.readline().strip()
# تا زمانی که خط خالی نباشد
while ligne != '':
# ما خط را در یک آرایه قرار میدهیم
infos = ligne.split(":")
# نام کاربری را بازیابی کنید
login = infos[0]
# رمز عبور را نادیده میگیریم
infos[0:2] = []
# ایجاد یک مدخل در فرهنگ لغت
dico[login] = infos
# خط بعدی را بخوانید
ligne = fic.readline().strip()
except IOError as erreur:
print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
sys.exit()
finally:
# اگر فایل باز شده باشد، آن را ببندید
if fic:
fic.close()
# فرهنگ «dico» را پردازش کنید
affiche_infos(dico, "login10")
affiche_infos(dico, "X")
یادداشتها:
- خط ۲۸: فایل را برای نوشتن باز میکند (w=write). اگر فایل از قبل وجود داشته باشد، روی آن نوشته میشود؛
- خطوط ۳۰–۳۴: ۱۰۰ خط در فایل متنی تولید میشوند؛
- خط ۳۴: برای نوشتن یک خط در فایل متنی. متد [write] کاراکتر پایان خط را اضافه نمیکند. بنابراین شما باید این کاراکتر را در متنی که مینویسید بگنجانید؛
- خطوط ۳۵–۳۷: رسیدگی به هرگونه استثنا؛
- خط ۳۷: اجرای اسکریپت را متوقف میکند (اگرچه تنها پس از اجرای بلوک
finally). - خطوط ۳۸–۴۱: در همه موارد، صرفنظر از اینکه خطایی رخ دهد یا خیر، اگر فایل باز باشد، بسته میشود؛
- خط ۴۷: باز کردن فایل برای خواندن (r=خواندن);
- خط ۴۹: تعریف یک دیکشنری خالی؛
- خط ۵۲: متد [readline] یک خط متن را، شامل کاراکتر پایان خط، میخواند. متد [strip] فاصلهها را در ابتدا و انتهای رشته حذف میکند. منظور از «فاصله» حروف فضای سفید، کاراکترهای پایان خط، شکست صفحه، تبها و چند مورد دیگر است. بنابراین در اینجا، متد [ligne] شامل کاراکترهای پایان خط موجود در [\r\n] (ویندوز) یا [\n] (یونیکس) نخواهد بود؛
- خط ۵۴: فایل تا زمانی که یک خط خالی یافت شود پردازش میشود؛
- خطوط ۵۴–۶۴: فایل متنی به فرهنگ لغت [dico] منتقل میشود. کلید، فیلد [login] است و مقدار، فیلدهای [uid:gid:infos:dir:shell] هستند؛
- خطوط ۶۵–۶۷: رسیدگی به هرگونه استثنا؛
- خطوط ۶۸–۷۱: فایل در همه موارد بسته میشود، چه خطایی رخ دهد و چه ندهد؛
- سطور ۷۴–۷۵: پردازش فرهنگ لغت [dico];
فایل [data/infos.txt]:
خروجی صفحه:
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/fic_01.py
login10 : ['uid10', 'gid10', 'infos10', 'dir10', 'shell10']
la clé [X] n'existe pas
Process finished with exit code 0
7.2. اسکریپت [fic_02]: پردازش فایلهای متنی رمزگذاریشده در UTF-8
در ادامه این سند، ما با فایلهای متنی که صرفاً در UTF-8 رمزگذاری شدهاند، کار خواهیم کرد. ابتدا، PyCharm را پیکربندی خواهیم کرد:

- در [5-6]: رمزگذاری UTF-8 را برای فایلهای پروژه انتخاب کنید؛
برای ایجاد یک فایل رمزگذاریشده در UTF-8، به شرح زیر عمل کنید (fic-02):
# وارداتها
import codecs
# نوشتن UTF-8 در یک فایل متنی
# استثناها مدیریت نمیشوند
file=codecs.open("./data/utf8.txt","w","utf8")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
یادداشتها
- خط ۲: برای مدیریت رمزگذاری فایل، ماژول [codecs] را وارد کنید؛
- خط ۶: متد [codecs.open] به همان شیوه تابع استاندارد [open] استفاده میشود. با این حال، شما میتوانید کدگذاری مورد نظر (برای ایجاد) یا کدگذاری موجود (برای خواندن) را مشخص کنید. پس از باز کردن، شیء [file] که در خط ۶ به دست آمده است، مانند یک فایل استاندارد استفاده میشود؛
- خط ۷: از کاراکترهای دارای آکسنت استفاده شده است که معمولاً بسته به مجموعه کاراکتری مورد استفاده، نمایشهای متفاوتی دارند؛
نتایج
وقتی فایل حاصل [data/utf8.txt] (رجوع شود به خط ۶) باز میشود، نتیجه زیر به دست میآید:

7.3. اسکریپت [fic_03]: پردازش فایلهای متنی رمزگذاریشده در ISO-8859-1
اسکریپت [fic_03] همان کاری را انجام میدهد که اسکریپت [fic_02] انجام میدهد اما فایل متنی را در ISO-8859-1 رمزگذاری میکند. ما میخواهیم تفاوت بین فایلهای حاصل را نشان دهیم:
# واردات
import codecs
#نوشتن در فرمت ISO-8859-1 به یک فایل متنی
# استثناها مدیریت نمیشوند
file=codecs.open("./data/iso-8859-1.txt","w","iso-8859-1")
file.write("Hélène est partie à Bâle pendant l'été chez sa grand-mère")
file.close()
وقتی فایل [data/iso-8859-1] ایجادشده در خط ۶ را باز میکنیم، نتیجهٔ زیر را میگیریم:

از آنجا که ما پروژه را برای کار با فایلهای UTF-8 پیکربندی کردهایم، PyCharm تلاش کرد فایل [iso-8859-1.txt] را به عنوان UTF-8 باز کند. میتواند تشخیص دهد که فایل UTF-8 نیست. بنابراین پیشنهاد میکند فایل را با یک رمزگذاری متفاوت بارگذاری مجدد کنید:

- به صورت [3-5]: فایل با استفاده از رمزگذاری ISO-8859-1 دوباره بارگذاری میشود؛

- به [6]، همان فایل اما با یک رمزگذاری متفاوت نمایش داده میشود؛
اگر به تنظیمات پروژه بازگردیم:

- میتوانیم ببینیم که برای [6-7]، PyCharm متوجه شده است که فایل [iso-8859-1.txt] باید با کدگذاری ISO-8859-1 باز شود. بنابراین این یک استثنا برای قاعده [5] است؛
7.4. اسکریپت [json_01]: رسیدگی به یک فایل jSON
JSON مخفف JavaScript Object Notation است. همانطور که از نامش پیداست، این یک نمایش مبتنی بر متن از اشیاء جاوااسکریپت است. ما در اینجا از آن با اشیاء پایتون استفاده خواهیم کرد.
فایل jSON که توسط [data/in.json] پردازش شده است، به شکل زیر خواهد بود:

- در [2]، میتوانیم ببینیم که محتوای متنی فایل [in.json] میتواند یک دیکشنری پایتون را نشان دهد. PyCharm این متن را قالببندی (Ctrl-Alt-L) کرده است، اما حتی اگر در یک خط واحد هم بود، تفاوتی ایجاد نمیکرد. قالب متن تا زمانی که از نظر نحوی یک شیء پایتون را نشان دهد، بیاهمیت است؛
اسکریپت [json-01] نشان میدهد که چگونه از این فایل استفاده کنیم:
# واردات
import codecs
import json
import sys
#خواندن/نوشتن یک فایل jSON
inFile=None
outFile=None
try:
#باز کردن فایل jSON در حالت خواندن
inFile = codecs.open("./data/in.json", "r", "utf8")
#انتقال محتویات به یک فرهنگ لغت
data = json.load(inFile)
#نمایش دادههای خواندهشده
print(f"data={data}, type(data)={type(data)}")
limites = data['limites']
print(f"limites={limites}, type(limites)={type(limites)}")
print(f"limites[1]={limites[1]}, type(limites[1])={type(limites[1])}")
#انتقال فرهنگ لغت [data] به یک فایل JSON
outFile = codecs.open("./data/out.json", "w", "utf8")
json.dump(data, outFile)
except BaseException as erreur:
#خطا را نمایش داده و خروج میکند
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# هر فایل باز را ببندید
if inFile:
inFile.close()
if outFile:
outFile.close()
یادداشتها
- خط ۳: برای رسیدگی به JSON، ما ماژول [json] را وارد میکنیم؛
- خط ۱۱: ما قصد داریم فایلهای jSON را که در UTF-8 رمزگذاری شدهاند، مدیریت کنیم. در اینجا، ما فایل [data/in.json] را با استفاده از ماژول [codecs] باز میکنیم؛
- خط ۱۳: متد [json.load] محتویات فایل jSON را میخواند و آنها را در متغیر [data] ذخیره میکند. نوع این متغیر یک دیکشنری خواهد بود؛
- خطوط ۱۵–۱۸: برای اینکه نشان دهیم که در واقع یک دیکشنری پایتون به دست آوردهایم، چند مورد از عناصر آن را چاپ میکنیم؛
- خطوط ۲۰–۲۱: ما عملیات معکوس را انجام میدهیم: دیکشنری [data] با استفاده از متد [json.dump] در فایلی به نام UTF-8 نوشته میشود؛
- خطوط ۲۲–۲۵: رسیدگی به هرگونه استثنا؛
- خطوط ۲۶–۳۱: در همه موارد، صرفنظر از اینکه خطایی رخ دهد یا خیر، هر فایلی که ممکن است باز شده باشد، بسته میشود؛
نتایج
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/fichiers/json_01.py
data={'limites': [9964, 27519, 73779, 156244, 0], 'coeffR': [0, 0.14, 0.3, 0.41, 0.45], 'coeffN': [0, 1394.96, 5798, 13913.69, 20163.45], 'PLAFOND_QF_DEMI_PART': 1551, 'PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION': 21037, 'PLAFOND_REVENUS_COUPLE_POUR_REDUCTION': 42074, 'VALEUR_REDUC_DEMI_PART': 3797, 'PLAFOND_DECOTE_CELIBATAIRE': 1196, 'PLAFOND_DECOTE_COUPLE': 1970, 'PLAFOND_IMPOT_COUPLE_POUR_DECOTE': 2627, 'PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE': 1595, 'ABATTEMENT_DIXPOURCENT_MAX': 12502, 'ABATTEMENT_DIXPOURCENT_MIN': 437}, type(data)=<class 'dict'>
limites=[9964, 27519, 73779, 156244, 0], type(limites)=<class 'list'>
limites[1]=27519, type(limites[1])=<class 'int'>
Process finished with exit code 0
- خطوط ۲ تا ۴ نشان میدهند که دیکشنری موجود در فایل jSON با موفقیت بازیابی شده است؛
اکنون، بیایید محتویات فایل [data/out.json] را بررسی کنیم:

متن داخل فایل در یک خط واحد قرار دارد. با این حال، PyCharm فایلهای jSON را تشخیص میدهد و میتوانیم آنها را مانند فایلهای پایتون و سایرین با استفاده از Ctrl-Alt-L قالببندی کنیم. سپس خروجی زیر را دریافت میکنیم:

7.5. اسکریپت [json_02]: پردازش فایلهای jSON رمزگذاریشده در UTF-8
یک فایل jSON که در UTF-8 رمزگذاری شده باشد، میتواند دو شکل داشته باشد:
# وارد میکند
import codecs
import json
import sys
# واژهنامه
data = {'marié': 'oui', 'impôt': 1340}
#نوشتن یک فایل jSON
out_file1 = None
out_file2 = None
try:
#صادر کردن فرهنگ لغت [data] به یک فایل JSON
out_file1 = codecs.open("./data/out1.json", "w", "utf8")
json.dump(data, out_file1, ensure_ascii=True)
#صادر کردن فرهنگ لغت [data] به یک فایل JSON
out_file2 = codecs.open("./data/out2.json", "w", "utf8")
json.dump(data, out_file2, ensure_ascii=False)
except BaseException as erreur:
#خطا را نمایش داده و خروج میکند
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
#بستن هر فایل باز
if out_file1:
out_file1.close()
if out_file2:
out_file2.close()
…
- در این اسکریپت، دیکشنری [data] (خط ۷) در دو فایل jSON (خطوط ۱۴ و ۱۷) نوشته میشود؛
- خطوط ۱۴ و ۱۷: در هر دو مورد، یک فایل متنی به نام UTF-8 ایجاد میشود؛
- خط ۱۵: هنگام نوشتن فرهنگ لغت، از پارامتر با نام [ensure_ascii=True] استفاده میشود؛
- خط ۱۸: هنگام نوشتن فرهنگ لغت، از پارامتر با نام [ensure_ascii=False] استفاده میشود؛
در اینجا دو فایل حاصل آمده است:

- در فایل [out1.json]، کاراکترهای دارای آکسنت با دنبالهای از کاراکترها که نماینده کد آنها UTF-8 است، جایگزین شدهاند. به این عمل گاهی «escaping» گفته میشود. از نظر فنی، در فایل باینری [out1.json]، کاراکتر «é» از [marié] به ترتیب با کدهای باینری UTF-8 از شش کاراکتر [\u00e9] نشان داده میشود؛
- در فایل [out2.json]، حروف دارای آکسانت بدون تغییر باقی ماندهاند. این بدان معناست که در باینری [out2.json]، این حروف با کد باینری خود UTF-8 نمایش داده شدهاند (فقط یک کد واحد، UTF-8، به جای ۶ برای out1). برای کاراکتر «é» در [marié]، کد باینری [00e9] بنابراین در بیش از ۴ بایت یافت میشود؛
- این مقدار پارامتر [ensure_ascii] در متد [json.dump] است که فرمت مورد استفاده را تعیین میکند؛
برخی برنامهها برای فایلهای jSON خود از UTF-8 «فرار داده شده» استفاده میکنند. در این صورت، باید از مقدار [ensure_ascii=True] استفاده شود. این مقدار در واقع پیشفرض است. بنابراین، اگر پارامتر [ensure_ascii] استفاده نشود، سیستم با فایلهای «escaped» jSON و UTF-8 کار خواهد کرد.
اسکریپت به شرح زیر ادامه مییابد:
# وارد میکند
import codecs
import json
import sys
# واژهنامه
data = {'marié': 'oui', 'impôt': 1340}
…
# بازخوانی فایلهای jSON
in_file1 = None
in_file2 = None
try:
#انتقال فایل jSON 1 به یک فرهنگ لغت
in_file1 = codecs.open("./data/out1.json", "r", "utf8")
dico1 = json.load(in_file1)
#نمایش
print(f"dico1={dico1}")
#انتقال فایل jSON 2 به یک فرهنگ لغت
in_file2 = codecs.open("./data/out2.json", "r", "utf8")
dico2 = json.load(in_file2)
# نمایش
print(f"dico2={dico2}")
except BaseException as erreur:
# خطا را نمایش داده و خروج
print(f"L'erreur suivante s'est produite : {erreur}")
sys.exit()
finally:
# بستن هر فایل باز
if in_file1:
in_file1.close()
if in_file2:
in_file2.close()
یادداشتها
- خطوط ۱۱–۳۴: بازخوانی مجدد دو فایل [out1.json, out2.json] و نمایش فرهنگ لغت خوانده شده در هر مورد؛
نتایج
به طور شگفتآور، دریافتیم که نیازی نبود تا نوع رمزگذاری (escaped یا نه) رشته jSON را که قرار است خوانده شود، به تابع [json.load] (خطوط 17، 22) مشخص کنیم. در هر دو مورد، واژهنامه صحیح بازیابی میشود.