Python: Читання/запис файлів

Прикладна аналітика при розробці IT

Ігор Мірошниченко

КНУ імені Тараса Шевченка, ФІТ

Читання та запис файлів

Запис даних

До цього часу більшість програм, які ми писали, просто зберігали всю інформацію в пам’яті, тобто в змінних або всередині самої програми.

Недоліком цього є те, що як тільки програма завершує роботу, все, що ви ввели, все, що ви робили з цією програмою, втрачається.

Використовуючи файли, ви можете зберігати інформацію довгостроково, і введення/виведення файлів (англ. file I/O) в контексті програмування - це написання коду, який може читати з файлів, тобто завантажувати інформацію з них, або записувати до них, тобто зберігати інформацію у самих файлах.

Запис даних

Для початку пропоную розглянути знайому структуру даних, яку ми бачили раніше - list.

Створимо програму names.py, яка буде зберігати імена у списку, а потім виводити їх на екран:

Terminal
code names.py


name = input("Як Вас звати? ")
print(f'Привіт, {name}!')

Запис даних

Припустімо, що ми хочемо додати підтримку збереження декількох імен, наприклад трьох. Для цього ми можемо використати список. Для цього необхідно створити пустий список names і додавати (append) до нього імена, які вводить користувач. Вивід імен відсортуємо за алфавітом:

names = []

for _ in range(3):
    names.append(input("Як Вас звати? "))

for name in sorted(names):
    print(f'Привіт, {name}!')
Як Вас звати? Гаррі
Як Вас звати? Рон
Як Вас звати? Герміона
Привіт, Гаррі!
Привіт, Герміона!
Привіт, Рон!

Звичайно, якщо я запущу цю програму ще раз, всі імена пропадуть. Було б непогано мати можливість якось зберігати цю інформацію. І саме тут з’являється ввід-вивід файлів, і саме тут з’являються файли.

Запис даних

Давайте перепишемо нашу програму так, щоб вона зберігала імена у файлі names.txt.

Для цього нам необхідно відкрити файл, використовуючи функцію open() - ця функція приймає два аргументи: ім’я файлу і режим відкриття.

Режим відкриття може бути:

  • r (англ. read) - читання, це режим за замовчуванням.
  • w (англ. write) - запис, цей режим перезаписує файл.
  • a (англ. append) - дописування, цей режим додає дані до файлу.

Якщо файл не існує, то він буде створений. Давайте перепишемо нашу програму з використанням функції open():

name = input("Як Вас звати? ")

file = open('names.txt', 'w')
file.write(name)
file.close()

Запис даних

Запустимо цю програму і перевіримо, чи вона працює:

Terminal
python names.py
Як Вас звати? Гаррі
code names.txt

Відкриємо створений файл:

names.txt
Гаррі

Все працює! Тепер давайте виконаємо цю програму ще раз, але цього разу введемо ім’я Рон:

Terminal
python names.py
Як Вас звати? Рон
code names.txt

Відкриємо створений файл:

names.txt
Рон

Як бачимо, файл перезаписався, і тепер в ньому знаходиться тільки ім’я Рон.

Запис даних

Якщо ми хочемо додати ім’я до файлу, а не перезаписати його, то використовуйте режим a.

Видаліть файл names.txt і давайте перепишемо нашу програму так, щоб вона дописувала імена до файлу names.txt:

Terminal
rm names.txt
remove names.txt? y
name = input("Як Вас звати? ")

file = open('names.txt', 'a')
file.write(name)
file.close()

Запустимо цю програму і перевіримо, чи вона працює:

Terminal
python names.py
Як Вас звати? Герміона
code names.txt
names.txt
Герміона

Запис даних

Запустимо програму ще раз і спробуємо додати ім’я Гаррі та Рон:

Terminal
python names.py
Як Вас звати? Гаррі
python names.py
Як Вас звати? Рон
code names.txt
names.txt
ГерміонаГарріРон

Зовсім не той результат, який ми очікували.

Імена записалися в один рядок. Це тому, що функція write не додає символ переносу рядка (\n) після запису імені.

Запис даних

Щоб це виправити, ми можемо додати символ переносу рядка після запису імені:

Terminal
rm names.txt
remove names.txt? y
name = input("Як Вас звати? ")

file = open('names.txt', 'a')
file.write(name + '\n')
file.close()

Запустимо цю програму:

Terminal
python names.py
Як Вас звати? Герміона
python names.py
Як Вас звати? Гаррі
python names.py
Як Вас звати? Рон
code names.txt
names.txt
Герміона
Гаррі
Рон

Примітка

Документація до функції open: https://docs.python.org/3/library/functions.html#open

Контекстний менеджер

Під час написання коду дуже легко забути закрити файли і це може стати проблемою. Тому ми можемо піти більш безпечним шляхом і використовувати контекстний менеджер.

Контекстний менеджер - це спеціальна конструкція мови Python, яка дозволяє виконувати певні дії до входу в блок коду і після виходу з блоку коду.

Для використання контекстного менеджера використовується ключове слово with. Давайте перепишемо нашу програму з використанням контекстного менеджера:

name = input("Як Вас звати? ")

with open('names.txt', 'a', encoding="utf8") as file:
    file.write(name + '\n')

Такий підхід не змінює функціональність програми, але є більш пітоничним.

Читання даних

Для читання, у функції open використовується режим r.

Давайте створимо програму names_read.py, яка буде читати імена з файлу names.txt і виводити їх на екран:

Terminal
code names_read.py

Для читання використаємо метод readlines, яка повертає список рядків, які містяться у файлі.

Цей метод повертає список, тому ми можемо використати цикл for для виведення імен на екран. Також слід врахувати, що метод readlines повертає список, в якому кожен рядок містить символ переносу рядка (\n).

Щоб цього уникнути, ми можемо використати метод rstrip, який видаляє символ переносу рядка з кінця рядка:

with open('names.txt', 'r', encoding="utf8") as file:
    lines = file.readlines()

for line in lines:
    print(f'Привіт, {line.rstrip()}!')
Привіт, Герміона!
Привіт, Гаррі!
Привіт, Рон!

Читання даних

Але у попередньому прикладі ми двічі проходимось по всьому файлу: спочатку ми читаємо його у список, а потім виводимо список на екран.

Це не є найкращим рішенням, оскільки ми можемо витратити багато пам’яті, якщо файл дуже великий.

Тому ми можемо використати цикл for безпосередньо для читання файлу:

with open('names.txt', 'r', encoding="utf8") as file:
    for line in file:
        print(f'Привіт, {line.rstrip()}!')
Привіт, Герміона!
Привіт, Гаррі!
Привіт, Рон!

Читання даних

Тепер трошки ускладнимо задачу.

Припустимо, що ми хочемо виводити привітання у алфавітному порядку. Для цього нам необхідно відсортувати список імен.

Для цього ми можемо використати функцію sorted(), яка повертає відсортований список:

names = []

with open('names.txt', 'r', encoding="utf8") as file:
    for line in file:
        names.append(line.rstrip())

for name in sorted(names):
    print(f'Привіт, {name}!')
Привіт, Гаррі!
Привіт, Герміона!
Привіт, Рон!

Читання даних

Ми можемо зробити цю програму більш компактною. Для цього ми можемо відсортувати сам файл:

with open('names.txt', 'r', encoding="utf8") as file:
    for line in sorted(file):
        print(f'Привіт, {line.rstrip()}!')
Привіт, Гаррі!
Привіт, Герміона!
Привіт, Рон!

Для зворотного сортування ми можемо використати параметр reverse функції sorted:

with open('names.txt', 'r', encoding="utf8") as file:
    for line in sorted(file, reverse=True):
        print(f'Привіт, {line.rstrip()}!')
Привіт, Рон!
Привіт, Герміона!
Привіт, Гаррі!

Примітка

Документація до функції sorted: https://docs.python.org/3/library/functions.html#sorted

Файли csv

Файли csv (англ. comma-separated values, значення, розділені комами) - це файли, які містять дані у вигляді таблиці, де значення розділені комами.

Давайте створимо файл students.csv:

Terminal
code students.csv

Запишемо у нього імена і додамо гуртожиток:

students.csv
Гаррі,Гріфіндор
Герміона,Гріфіндор
Рон,Гріфіндор
Драко,Слизерин

Файли csv

Тепер давайте створимо програму students.py, яка буде читати цей файл.

Terminal
code students.py

Ми можемо використати метод split для розділення рядка на частини. Давайте перепишемо нашу програму з використанням методу split:

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        row = line.rstrip().split(',')
        print(f'{row[0]} живе в гуртожитку {row[1]}')
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор
Драко живе в гуртожитку Слизерин

Файли csv

Коли у вас є змінна, яка є списком, наприклад row, вам не обов’язково переносити всі ці змінні у окремий список.

Ви можете розпакувати всю послідовність одразу.

Іншими словами, якщо ви знаєте, що функція типу split повертає список, який містить два елементи, ви можете розпакувати цей список у дві змінні:

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        print(f'{name} живе в гуртожитку {house}')
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор
Драко живе в гуртожитку Слизерин

Файли csv

Уявімо, що нам треба відсортувати цей список даних.

Для цього я можу використати функцію sorted() і вказати, що я хочу сортувати за другим елементом списку:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        students_lst.append(f'{name} живе в гуртожитку {house}')

for student in sorted(students_lst):
    print(student)
Драко живе в гуртожитку Слизерин
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор

Файли csv

З технічної точки зору, це працює, але це не є найкращим рішенням, оскільки дані сортуються по цілому реченню.

Ми можемо вирішити таку задачу за допомогою словників.

Для цього нам необхідно створити пустий словник student_dict і додавати до нього інформацію про студентів:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        student_dict = {}
        student_dict['name'] = name
        student_dict['house'] = house
        students_lst.append(student_dict)

for student in students_lst:
    print(f'{student["name"]} живе в гуртожитку {student["house"]}')

Файли csv

Ми можемо скоротити код шляхом присвоєння значень словнику одразу:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        student_dict = {'name': name, 'house': house}
        students_lst.append(student_dict)

for student in students_lst:
    print(f'{student["name"]} живе в гуртожитку {student["house"]}')

Файли csv

Але результат все ще не відсортований.

Функція sorted приймає параметр key, який вказує, за яким ключем сортувати.

Для цього ми можемо використати функцію get_name, яка повертає ім’я студента і використаємо її як параметр key:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        student_dict = {'name': name, 'house': house}
        students_lst.append(student_dict)

def get_name(student):
    return student['name']

for student in sorted(students_lst, key=get_name):
    print(f'{student["name"]} живе в гуртожитку {student["house"]}')
Драко живе в гуртожитку Слизерин
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор

Файли csv

Якщо ж я захочу відсортувати за гуртожитком у зворотному порядку, то я можу використати функцію get_house і додати параметр reverse=True у функцію sorted:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        student_dict = {'name': name, 'house': house}
        students_lst.append(student_dict)

def get_house(student):
    return student['house']

for student in sorted(students_lst, key=get_house, reverse=True):
    print(f'{student["name"]} живе в гуртожитку {student["house"]}')
Драко живе в гуртожитку Слизерин
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор

Попередження

Зверніть увагу, що в якості аргументу key функції sorted ми передаємо функцію get_house, без дужок. Ми хочемо передати функцію, а не викликати її.

Анонімні функції

У попередньому прикладі ми використовували функції get_name і get_house, які одразу використовуємо і більші ніколи до них не повертаємось.

Ми можемо спростити цей код і використати анонімні функції (англ. lambda functions), які дозволяють нам визначити функцію в одному рядку:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, house = line.rstrip().split(',')
        student_dict = {'name': name, 'house': house}
        students_lst.append(student_dict)

for student in sorted(students_lst, key=lambda student: student['name']):
    print(f'{student["name"]} живе в гуртожитку {student["house"]}')
Драко живе в гуртожитку Слизерин
Гаррі живе в гуртожитку Гріфіндор
Герміона живе в гуртожитку Гріфіндор
Рон живе в гуртожитку Гріфіндор

Пакет csv

Читання csv-файлів

Давайте змінимо файл students.csv і замінимо гуртожитки на будинки де вони виросли:

students.csv
Гаррі,Тисова, 4
Рон,Нора
Драко,Маєток Мелфоїв

Тепер давайте виведемо ці дані на екран:

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    for line in file:
        name, home = line.rstrip().split(',')
        student_dict = {'name': name, 'home': home}
        students_lst.append(student_dict)

for student in sorted(students_lst, key=lambda student: student['home']):
    print(f'{student["name"]} з {student["home"]}')
ValueError: too many values to unpack (expected 2)

У нас виникла помилка. Це тому, що у нас є рядок, який містить дві коми, а ми спробували розпакувати його у дві змінні. Для вирішення цієї проблеми ми можемо використати в якості роздільника якийсь менш популярний символ, наприклад |:

students.csv
Гаррі|Тисова, 4
Рон|Нора
Драко|Маєток Мелфоїв

Пакет csv

Інший варіант - це помістити значення у лапки:

students.csv
Гаррі,"Тисова, 4"
Рон,Нора
Драко,"Маєток Мелфоїв"

В будь-якому випадку необхідно буде змінювати код і продумувати логіку читання файлу. І це стає дуже незручним і складним, якщо у вас є багато різних файлів, які містять дані у різних форматах. Тому для роботи з csv-файлами використовують спеціальний пакет csv. Давайте перепишемо нашу програму з використанням пакету csv:

import csv

students_lst = []

with open('students.csv', 'r', encoding="utf8") as file:
    reader = csv.reader(file)
    for row in reader:
        student_dict = {'name': row[0], 'home': row[1]}
        students_lst.append(student_dict)

for student in sorted(students_lst, key=lambda student: student['home']):
    print(f'{student["name"]} з {student["home"]}')

Якщо ми чітко знаємо кількість стовпчиків у .csv-файлі, ми можемо розпакувати рядки одразу у змінні:

import csv

students_lst = []

with open('students.csv', encoding="utf8") as file:
    reader = csv.reader(file)
    for name, home in reader:
        students_lst.append({'name': name, 'home': home})

for student in sorted(students_lst, key=lambda student: student['home']):
    print(f'{student["name"]} з {student["home"]}')
Драко з Маєток Мелфоїв
Рон з Нора
Гаррі з Тисова, 4

Пакет csv

Часто у табличних файлах перший рядок = за назву змінних. Давайте додамо name та home у students.csv:

students.csv
name,home
Гаррі,"Тисова, 4"
Рон,Нора
Драко,Маєток Мелфоїв

В таких випадках ми можемо використати функцію DictReader, яка повертає словник, а не список:

import csv

students_lst = []

with open('students.csv', encoding="utf8") as file:
    reader = csv.DictReader(file)
    for row in reader:
        students_lst.append({'name': row['name'], 'home': row['home']}) # або students_lst.append(row)

for student in sorted(students_lst, key=lambda student: student['home']):
    print(f'{student["name"]} з {student["home"]}')
Драко з Маєток Мелфоїв
Рон з Нора
Гаррі з Тисова, 4

Такий підхід є більш стійким до змін у файлі: хтось міг змінити порядок стовпчиків, але програма все одно буде працювати.

Примітка

Документація до пакету csv: https://docs.python.org/3/library/csv.html

Пакет csv

Запис csv-файлів

Припустимо, що ми створюємо програму, яка буде записувати дані про студентів у файл students.csv. Залишимо у файлі students.csv наступні дані:

students.csv
name,home

Давайте перепишемо програму students.py, яка буде записувати дані у файл students.csv:

import csv

name = input('Як Вас звати? ') # Гаррі
home = input('Де Ви живете? ') # Тисова, 4

with open('students.csv', 'a', encoding="utf8") as file:
    writer = csv.writer(file)
    writer.writerow([name, home])

Запустимо цю програму:

Terminal
python students.py
Як Вас звати? Гаррі
Де Ви живете? Тисова, 4

Відкриємо файл students.csv:

students.csv
name,home
Гаррі,"Тисова, 4"

Як бачите, Python автоматично взяв рядок з комою у лапки щоб уникнути помилки.

Пакет csv

Запис csv-файлів

Існує ще один спосіб реалізувати програму students.py не турбуючись про порядок змінних у списку. Для цього ми можемо використати функцію DictWriter, яка дозволяє записувати дані у файл у вигляді словника:

import csv

name = input('Як Вас звати? ') # Драко
home = input('Де Ви живете? ') # Маєток Мелфоїв

with open('students.csv', 'a', encoding="utf8") as file:
    writer = csv.DictWriter(file, fieldnames=['name', 'home'])
    writer.writerow({'name': name, 'home': home})

Запустимо цю програму:

Terminal
python students.py
Як Вас звати? Драко
Де Ви живете? Маєток Мелфоїв

Відкриємо файл students.csv:

students.csv
name,home
Гаррі,"Тисова, 4"
Драко,Маєток Мелфоїв

Бінарні файли

Бінарні файли - це файл, який складається лише з нулів та одиниць і дозволяє зберігати будь-які дані: зображення, відео, звук, текст, тощо.

В Python є популярна бібліотека під назвою pillow, яка дозволяє працювати з зображеннями, застосовувати фільтри, як в Instagram, створювати анімації, тощо.

Примітка

Документація до пакету PIL: https://pillow.readthedocs.io

Давайте створимо анімоване GIF-зображення. Сьогодні такі файли зустрічаються скрізь у вигляді мемів, анімацій, наклейок тощо. Анімоване GIF-зображення – це графічний файл, який містить кілька зображень всередині, а комп’ютер показує їх одне за одним, створюючи ефект анімації.

Бінарні файли

Почнемо з двох статичних зображень:

Terminal
code costume1.gif
code costume2.gif

(a) costume1.gif

(b) costume2.gif

Рисунок 1: Статичні зображення

Примітка

Ці коти походять з мови програмування MIT під назвою Scratch.

Посилання на зображення ви можете знайти у репозиторії: https://github.com/Aranaur/py4ds/tree/main/img/python

Бінарні файли

Тепер створимо файл costume.py, який буде об’єднувати ці два зображення у анімацію:

Terminal
code costume.py

Для цього нам необхідно використати функцію Image.open, яка дозволяє відкрити зображення, а потім використати метод save, який дозволяє зберегти зображення у форматі GIF:

import sys
from PIL import Image

images_lst = []

for arg in sys.argv[1:]:
    image = Image.open(arg)
    images_lst.append(image)

images_lst[0].save(
    'costume.gif',
    save_all=True,
    append_images=images_lst[1:],
    duration=200,
    loop=0)

Бінарні файли

Згустимо програму:

Terminal
python costume.py costume1.gif costume2.gif

Відкриємо файл costume.gif:

Terminal
code costume.gif

Рисунок 2: costume.gif

Дякую за увагу!



Матеріали курсу

ihor.miroshnychenko@kneu.ua

Data Mirosh

@ihormiroshnychenko

@aranaur

aranaur.rbind.io