Skip to content

7. Текстові файли

Image

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")

Примітки:

  • рядок 28: відкриття файлу для запису (w=write). Якщо файл уже існує, його буде перезаписано;
  • рядки 30–34: у текстовому файлі генерується 100 рядків;
  • рядок 34: запис рядка у текстовий файл. Метод [write] не додає символ кінця рядка. Тому його потрібно вказати у записуваному тексті;
  • рядки 35–37: обробка можливого винятку;
  • рядок 37: припинення виконання скрипта (проте після виконання блоку finally);
  • рядки 38–41: у будь-якому випадку, незалежно від наявності помилки, файл закривається, якщо він відкритий;
  • рядок 47: відкриття файлу для читання (r=read);
  • рядок 49: визначення порожнього словника;
  • рядок 52: метод [readline] зчитує рядок тексту, включаючи символ кінця рядка. Метод [strip] видаляє «пробіли» на початку та в кінці рядка. Під «пробілом» слід розуміти пробільний символ, символ кінця рядка, перехід на новий сторінку, табуляцію та деякі інші символи. Отже, у цьому випадку [ligne] не міститиме символів кінця рядка [\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):


# імпорт
import codecs

# запис у форматі utf8 у текстовий файл
# винятки не обробляються
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. Покажемо різницю між отриманими файлами:


# імпорт
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], створений у рядку 6, отримуємо такий результат:

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] демонструє, як використовувати цей файл:


# імпорт
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()

Примітки

  • рядок 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, може мати два варіанти:


# імпорт
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] (рядок 7) записується у два файли jSON (рядки 14, 17);
  • рядки 14, 17: в обох випадках створюється текстовий файл UTF-8;
  • рядок 15: під час запису словника використовується параметр із назвою [ensure_ascii=True];
  • рядки 18: під час запису словника використовується параметр із назвою [ensure_ascii=False];

Ось два отримані файли:

Image

  • у файлі [out1.json] символи з діакритичними знаками були замінені на послідовність символів, що позначають їхній код UTF-8. Іноді кажуть, що вони були «ескейповані». З технічної точки зору, у бінарному файлі [out1.json] для символу «é» з файлу [marié] послідовно містяться бінарні коди UTF-8 шести символів [\u00e9];
  • у файлі [out2.json] символи з діакритичними знаками були залишені без змін. Це означає, що у двійковому файлі [out2.json] ці символи представлені їхнім двійковим кодом UTF-8 (тобто лише 1 код UTF-8 замість 6 для out1). Для символу «é» у файлі [marié] ми, таким чином, знайдемо двійковий код [00e9], що займає 4 байти;
  • саме значення параметра [ensure_ascii] методу [json.dump] визначає формат, що використовується;

Деякі програми використовують «ескейп-код» UTF-8 для своїх файлів jSON. У цьому випадку слід використовувати значення [ensure_ascii=True]. Це значення, власне, є значенням за замовчуванням. Отже, якщо не використовувати параметр [ensure_ascii], то працюватимемо з файлами 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()

Примітки

  • рядки 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.load] (рядки 17, 22) тип кодування (з екрануванням чи без) рядка jSON, який потрібно прочитати. В обох випадках отримуємо правильний словник.