Skip to content

7. الملفات النصية

Image

7.1. النص البرمجي [fic_01]: قراءة/كتابة ملف نصي

يوضح البرنامج النصي التالي مثالاً على العمل مع الملفات النصية:

#  imports
import sys


#  creation and sequential processing of a text file
#  this is a set of lines of the form login:pwd:uid:gid:infos:dir:shell
#  each line is put into a dictionary in the form login => uid:gid:infos:dir:shell

# --------------------------------------------------------------------------
def affiche_infos(dico: dict, clé: str):
    #  displays the value associated with key in the dico dictionary if it exists
    if clé in dico.keys():
        #  displays the value associated with the key
        print(f"{clé} : {dico[clé]}")
    else:
        #  key is not a dictionary key dico
        print(f"la clé [{clé}] n'existe pas")


#  main -----------------------------------------------
#  set the file name
FILE_NAME = "./data/infos.txt"

#  creating and filling text files
fic = None
try:
    #  open file for writing (w=write)
    fic = open(FILE_NAME, "w")
    #  generate arbitrary content
    for i in range(1, 101):
        #  a line
        ligne = f"login{i}:pwd{i}:uid{i}:gid{i}:infos{i}:dir{i}:shell{i}"
        #  is written to the text file
        fic.write(f"{ligne}\n")
except IOError as erreur:
    print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
    sys.exit()
finally:
    #  close the file if it has been opened
    if fic:
        fic.close()

#  open it for reading
fic = None
try:
    #  open file for reading
    fic = open(FILE_NAME, "r")
    #  empty dictionary at start
    dico = {}
    #  each line is put into the [dico] dictionary as login => uid:gid:infos:dir:shell
    #  read 1st line, removing leading and trailing spaces
    ligne = fic.readline().strip()
    #  as long as the line is not empty
    while ligne != '':
        #  put the line in a table
        infos = ligne.split(":")
        #  retrieve login
        login = infos[0]
        #  we neglect the pwd
        infos[0:2] = []
        #  create a dictionary entry
        dico[login] = infos
        #  read next line
        ligne = fic.readline().strip()
except IOError as erreur:
    print(f"Erreur d'exploitation du fichier {FILE_NAME} : {erreur}")
    sys.exit()
finally:
    #  close the file if it has been opened
    if fic:
        fic.close()

#  using the dico dictionary
affiche_infos(dico, "login10")
affiche_infos(dico, "X")

ملاحظات:

  • السطر 28: يفتح الملف للكتابة (w=write). إذا كان الملف موجودًا بالفعل، فسيتم استبداله؛
  • الأسطر 30–34: إنشاء 100 سطر في الملف النصي؛
  • السطر 34: لكتابة سطر في الملف النصي. لا تضيف طريقة [write] حرفًا لبدء سطر جديد. لذلك، يجب عليك تضمين هذا الحرف في النص المكتوب؛
  • الأسطر 35-37: معالجة أي استثناءات؛
  • السطر 37: إيقاف تنفيذ البرنامج النصي (ولكن بعد تنفيذ كتلة finally
  • الأسطر 38-41: في جميع الحالات، سواء حدث خطأ أم لا، أغلق الملف إذا كان مفتوحًا؛
  • السطر 47: فتح الملف للقراءة (r=read
  • السطر 49: تعريف قاموس فارغ؛
  • السطر 52: تقرأ طريقة [readline] سطرًا من النص، بما في ذلك حرف نهاية السطر. تقوم طريقة [strip] بإزالة "المسافات" من بداية ونهاية السلسلة. ونقصد بـ "المسافات" أحرف المسافات البيضاء، وفواصل الأسطر، وفواصل الصفحات، وعلامات الجدولة، وبعض العناصر الأخرى. لذا، لن تحتوي [line] هنا على أحرف فاصل الأسطر [\r\n] (Windows) أو [\n] (Unix
  • السطر 54: تتم معالجة الملف حتى يتم العثور على سطر فارغ؛
  • الأسطر 54–64: يتم نقل الملف النصي إلى القاموس [dico]. المفتاح هو حقل [login]، والقيمة تتكون من الحقول [uid:gid:infos:dir:shell]؛
  • الأسطر 65–67: معالجة أي استثناءات؛
  • الأسطر 68–71: إغلاق الملف في جميع الحالات، سواء حدث خطأ أم لا؛
  • السطران 74-75: الاستعلام عن القاموس [dico]؛

الملف [data/infos.txt]:

1
2
3
4
5
6
login0:pwd0:uid0:gid0:infos0:dir0:shell0
login1:pwd1:uid1:gid1:infos1:dir1:shell1
login2:pwd2:uid2:gid2:infos2:dir2:shell2
login98:pwd98:uid98:gid98:infos98:dir98:shell98
login99:pwd99:uid99:gid99:infos99:dir99:shell99

إخراج الشاشة:


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:

Image

  • في [5-6]: حدد ترميز UTF-8 لملفات المشروع؛

لإنشاء ملف مشفر بـ UTF-8، اتبع الخطوات التالية (fic-02):

1
2
3
4
5
6
7
8
#  imports
import codecs

#  writing utf8 to a text file
#  exceptions are not handled
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()

ملاحظات

  • السطر 2: للتعامل مع ترميز الملف، نقوم باستيراد الوحدة النمطية [codecs]؛
  • السطر 6: تُستخدم طريقة [codecs.open] مثل الدالة القياسية [open]. ومع ذلك، يمكنك تحديد الترميز المطلوب (عند الإنشاء) أو الترميز الموجود (عند القراءة). بعد الفتح، يُستخدم كائن [file] الذي تم الحصول عليه في السطر 6 كملف قياسي؛
  • السطر 7: تم استخدام أحرف مشطوبة، والتي عادةً ما يكون لها تمثيلات مختلفة اعتمادًا على ترميز الأحرف المستخدم؛

النتائج

عند فتح ملف [data/utf8.txt] الذي تم الحصول عليه (انظر السطر 6)، يتم الحصول على النتيجة التالية:

Image

7.3. البرنامج النصي [fic_03]: معالجة الملفات النصية المشفرة بـ ISO-8859-1

يقوم البرنامج النصي [fic_03] بنفس ما يقوم به البرنامج النصي [fic_02] ولكنه يقوم بترميز الملف النصي في ISO-8859-1. نريد إظهار الفرق بين الملفات الناتجة:

1
2
3
4
5
6
7
8
#  imports
import codecs

#  iso-8859-1 writing in a text file
#  exceptions are not handled
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] الذي تم إنشاؤه في السطر السادس، نحصل على النتيجة التالية:

Image

نظرًا لأننا قمنا بتكوين المشروع للعمل مع ملفات UTF-8، حاول PyCharm فتح ملف [iso-8859-1.txt] بتنسيق UTF-8. ويمكنه أن يرى [1] أن الملف ليس بتنسيق UTF-8. ثم يقترح [2] إعادة تحميل الملف بترميز مختلف:

Image

  • في [3-5]: يتم إعادة تحميل الملف باستخدام ترميز ISO-8859-1؛

Image

  • في [6]، نفس الملف ولكن مع عرضه بترميز مختلف؛

إذا عدنا إلى إعدادات المشروع:

Image

  • نرى أنه في [6-7]، لاحظ PyCharm أن الملف [iso-8859-1.txt] يجب فتحه بترميز ISO-8859-1. وهذا يمثل استثناءً للقاعدة [5]؛

7.4. النص البرمجي [json_01]: العمل مع ملف JSON

JSON هي اختصار لـ JavaScript Object Notation. كما يوحي الاسم، فهو تمثيل نصي لكائنات JavaScript. هنا، سنستخدمه مع كائنات Python.

سيبدو ملف JSON الذي يتم إدارته [data/in.json] كما يلي:

Image

  • في [2]، يمكننا أن نرى أن المحتوى النصي لملف [in.json] يمكن أن يمثل قاموس Python. قام PyCharm بتنسيق (Ctrl-Alt-L) هذا النص، ولكن حتى لو كان في سطر واحد، فلن يحدث ذلك أي فرق. لا يهم تنسيق النص طالما أنه يمثل كائن Python من الناحية النحوية؛

يوضح البرنامج النصي [json-01] كيفية استخدام هذا الملف:

#  imports
import codecs
import json
import sys

#  read/write file jSON
inFile=None
outFile=None
try:
    #  open file jSON in read mode
    inFile = codecs.open("./data/in.json", "r", "utf8")
    #  transfer content to a dictionary
    data = json.load(inFile)
    #  display of read data
    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])}")
    #  transfer the [data] dictionary to a json file
    outFile = codecs.open("./data/out.json", "w", "utf8")
    json.dump(data, outFile)
except BaseException as erreur:
    #  display error and exit
    print(f"L'erreur suivante s'est produite : {erreur}")
    sys.exit()
finally:
    #  close files if they are open
    if inFile:
        inFile.close()
    if outFile:
        outFile.close()

ملاحظات

  • السطر 3: للعمل مع JSON، نقوم باستيراد الوحدة النمطية [json]؛
  • السطر 11: سنعمل مع ملفات JSON المشفرة بـ UTF-8. هنا، نفتح الملف [data/in.json] باستخدام وحدة [codecs]؛
  • السطر 13: تقرأ طريقة [json.load] محتويات ملف JSON وتخزنها في المتغير [data]. سيكون نوع هذا المتغير عبارة عن قاموس؛
  • الأسطر 15–18: للتحقق من أننا حصلنا بالفعل على قاموس Python، نعرض بعض عناصره؛
  • السطران 20-21: نقوم بإجراء العملية العكسية: يتم كتابة القاموس [data] إلى ملف مشفر بتنسيق UTF-8 باستخدام طريقة [json.dump]؛
  • السطور 22–25: معالجة أي استثناءات؛
  • السطور 26-31: في جميع الأحوال، سواء حدث خطأ أم لا، نقوم بإغلاق أي ملفات قد تكون مفتوحة؛

النتائج


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
  • تُظهر الأسطر 2–4 أننا نجحنا في استرداد القاموس من ملف JSON؛

الآن، دعونا نلقي نظرة على محتويات ملف [data/out.json]:

Image

النص الموجود في الملف مكتوب في سطر واحد. ومع ذلك، يتعرف PyCharm على ملفات JSON، ويمكننا تنسيقها — تمامًا مثل ملفات Python وغيرها — باستخدام Ctrl-Alt-L. وهذا يعطينا ما يلي:

Image

7.5. نص برمجي [json_02]: التعامل مع ملفات JSON المشفرة بـ UTF-8

يمكن أن يتخذ ملف JSON المشفر بـ UTF-8 شكلين:

#  imports
import codecs
import json
import sys

#  dictionary
data = {'marié': 'oui', 'impôt': 1340}

#  write a jSON file
out_file1 = None
out_file2 = None
try:
    #  transfer the [data] dictionary to a json file
    out_file1 = codecs.open("./data/out1.json", "w", "utf8")
    json.dump(data, out_file1, ensure_ascii=True)
    #  transfer the [data] dictionary to a json file
    out_file2 = codecs.open("./data/out2.json", "w", "utf8")
    json.dump(data, out_file2, ensure_ascii=False)
except BaseException as erreur:
    #  display error and exit
    print(f"L'erreur suivante s'est produite : {erreur}")
    sys.exit()
finally:
    #  close files if they are open
    if out_file1:
        out_file1.close()
    if out_file2:
        out_file2.close()

  • في هذا البرنامج النصي، يتم كتابة قاموس [data] (السطر 7) إلى ملفين JSON (السطران 14 و 17)؛
  • السطران 14 و 17: في كلتا الحالتين، يتم إنشاء ملف نصي بتنسيق UTF-8؛
  • السطر 15: عند كتابة القاموس، نستخدم المعلمة المسماة [ensure_ascii=True]؛
  • السطر 18: عند كتابة القاموس، نستخدم المعلمة المسماة [ensure_ascii=False]؛

فيما يلي الملفان الناتجان:

Image

  • في ملف [out1.json]، تم استبدال الأحرف المُشَدَّدة بسلسلة من الأحرف تمثل رمزها الثنائي في UTF-8. ويُشار إلى هذه العملية أحيانًا باسم "الهروب" (escaping). ومن الناحية الفنية، في النسخة الثنائية لملف [out1.json]، يتم تمثيل الحرف é في كلمة [marié] برموز UTF-8 الثنائية لستة أحرف متتالية وهي [\u00e9]؛
  • في ملف [out2.json]، تم ترك الأحرف المُشَدَّدة كما هي. وهذا يعني أنه في البيانات الثنائية لـ [out2.jsonيتم تمثيل هذه الأحرف برمزها الثنائي UTF-8 (رمز UTF-8 واحد فقط، بدلاً من 6 في [out1]). بالنسبة للحرف é في [marié]، نجد الرمز الثنائي المكون من 4 بايت [00e9]؛
  • إن قيمة المعلمة [ensure_ascii] الخاصة بالطريقة [json.dump] هي التي تحدد التنسيق المستخدم؛

تستخدم بعض التطبيقات UTF-8 "المهرب" لملفات JSON الخاصة بها. في هذه الحالة، يجب استخدام القيمة [ensure_ascii=True]. هذه القيمة هي في الواقع القيمة الافتراضية. لذلك، إذا لم يتم استخدام المعلمة [ensure_ascii]، فسنعمل مع ملفات JSON بتنسيق UTF-8 المهرب.

يستمر البرنامج النصي على النحو التالي:

#  imports
import codecs
import json
import sys

#  dictionary
data = {'marié': 'oui', 'impôt': 1340}



#  read back files jSON
in_file1 = None
in_file2 = None
try:
    #  transfer file jSON 1 to a dictionary
    in_file1 = codecs.open("./data/out1.json", "r", "utf8")
    dico1 = json.load(in_file1)
    #  display
    print(f"dico1={dico1}")
    #  transfer the jSON 2 file to a dictionary
    in_file2 = codecs.open("./data/out2.json", "r", "utf8")
    dico2 = json.load(in_file2)
    #  display
    print(f"dico2={dico2}")
except BaseException as erreur:
    #  display error and exit
    print(f"L'erreur suivante s'est produite : {erreur}")
    sys.exit()
finally:
    #  close files if they are open
    if in_file1:
        in_file1.close()
    if in_file2:
        in_file2.close()

ملاحظات

  • الأسطر 11–34: قراءة الملفين [out1.json، out2.json] وعرض القاموس الذي تمت قراءته في كل حالة؛

النتائج

1
2
3
4
5
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_02.py
dico1={'marié': 'oui', 'impôt': 1340}
dico2={'marié': 'oui', 'impôt': 1340}

Process finished with exit code 0

من المثير للدهشة أننا نرى أنه لم يكن من الضروري تحديد نوع الترميز (مع أو بدون هروب) لسلسلة JSON المراد قراءتها في دالة [json.load] (السطران 17 و22). في كلتا الحالتين، نسترد القاموس الصحيح.