__toc__
Мій ноутбук з Windows містив 38493 файли ще до того як я поставив хоч одну програму. Встановлення Python додало майже 3000 файлів до загальної суми. Файли є основною парадигмою зберігання даних в будь-якій поширеній операційній системі, ця ідея настільки вкоренилась, що більшості людей проблематично уявити альтернативу. Ваш комп'ютер, метафорично кажучи, потопає в файлах.
Читання текстових файлів Перед тим як зчитати щось з файла потрібно його відкрити. Відкриття файлів у Python не може бути простіше:
a_file = open('examples/chinese.txt', encoding='utf-8')
Python має вбудовану функцію open(), яка приймає як параметр ім'я файла. В цьому прикладі це 'examples/chinese.txt'. Назва файла має 5 цікавих особливостей:
Це не просто ім'я файла — це комбінація шляху та назви файла. Гіпотетично функція могла б мати 2 параметри — шлях та назву файла, але open() приймає лише один параметр. За потреби можна включити до імені файла частковий або повний шлях. Шлях використувує прямий слеш, але я не сказав яку операційну систему використовуємо. Windows використовує зворотні слеші для позначення директорій, а Mac OS X та Linux використовують прямі слеші. Але в Python, прямі слеші завжди Просто Працюють, навіть у Windows. Шлях не починається зі слешу або літери диску, тобто це відносний шлях. Ви можливо запитаєте відносно чого? Терпіння. Це рядок. Всі сучасні операційні системи (навіть Windows!) використовують Unicode для зберігання імен файлів та директорій. Python 3 повністю підтримує не-ASCII імена. Не потрібно щоб файл був на вашому локальному диску. Це може бути змонтований мережевий диск. Файл може бути елементом повністю віртуальної файлової системи. Якщо ваш комп'ютер вважає його файлом і має доступ до нього у вигляді файла, Python може спокійно відкрити його.
Але виклик функції open() не зупиняється на імені файла. Там ще один аргумент — encoding (кодування). Ой мамо, це звучить жахливо знайомо.
Кодування символів показує свою потворну голову Байти це байти, а символи це абстракція. Рядок — це послідовність символів Unicode. Але файл на диску не є послідовністю символів Unicode. Файл на диску — послідовність байтів. Отже, якщо ви читаєте текстовий файл з диску, як Python конвертує послідовність байтів у послідовність символів? Він декодує байти відповідно до певного алгоритму кодування символів і повертає послідовність символів Unicode (також відому як рядок).
# Цей приклад був створенний на Windows. Інші платформи # можуть поводити себе інакше (описано нижче) >>> file = open('examples/chinese.txt') >>> a_string = file.read() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode return codecs.charmap_decode(input,self.errors,decoding_table) UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 28: character maps to <undefined> >>>
Що трапилось? Ви не вказали кодування символів, тому Python використав кодування за замовчування. Що це за кодування? Якщо придивитись до повідомлення про помилку, то можна побачити, що це cp1252, звідки випливає що Python використовує кодування CP-1252 за замовчуванням (CP-1252 — кодування, яке часто використовується на комп'ютерах з Microsoft Windows). Кодування CP-1252 не підтримує символи, які знаходяться в цьому файлі, тому читання завершується з потворним UnicodeDecodeError.
Але зачекайте, тут ще гірше! Кодування за замовчуванням залежить від платформи, так що цей код може працювати на вашому комп'ютері (якщо за замовчуванням кодування UTF-8), але він не буде виконаний, якщо ви передасте його комусь ще (кодування за замовчуванням може відрізнятись від CP-1252).
Потоковий об'єкт Всі ми знаємо, що Python має вбудовану функцію open(). Ця функція повертає потоковий об'єкт, який має атрибути та методи для отримання інформації про потік символів файла, а також для маніпуляції над ним.
>>> a_file = open('examples/chinese.txt', encoding='utf-8') >>> a_file.name 'examples/chinese.txt'
Атрибут name містить ім'я файла, який ми передали як параметр у функцію open(). Ім'я файла не нормалізується до абсолютного шляху.
>>> a_file.encoding 'utf-8'
Крім того, атрибут encoding містить кодування, яке ви передали як параметр у фінкцію open(). Якщо при відкритті файла ви не вказали кодування (поганий розробник!), то атрибут буде містити значення locale.getpreferredencoding().
>>> a_file.mode 'r'
Атрибут mode містить інформацію про те, в якому режимі був відкритий файл. Ви можете передати необов'язковий параметр у функцію open(). Якщо ви не вказали режим, коли відкривали файл, то Python використає значення за замовчуванням 'r', яке означає "відкрити лише для читання, в текстовому режимі". Далі у цьому розділі, ви переконаєтесь, що режим файла служить для декількох цілей. Різні режими дозволяють вам перезаписувати файл, додавати дані до файла, чи відкрити файл у двійковому режимі (в цьому режимі ви маєте справу з байтами, а не рядками)
Читання даних з текстових файлів Якщо ви відкрили файл для читання, то можливо в певний момент ви захочете з нього щось прочитати.
>>> a_file = open('examples/chinese.txt', encoding='utf-8') >>> a_file.read() 'Dive Into Python 是为有经验的程序员编写的一本 Python 书。\n'
Коли ви відкрили файл (з правильним кодуванням), читання з нього здійснюється простим викликом методу read() потокового об'єкту. Результатом є текст.
>>> a_file.read() ''
Можливо дещо несподівано, але повторне прочитання файла не створює винятку. Python не розглядає читання даних після закінчення файла помилкою, він просто повертає порожній рядок.
А що якщо ми хочемо перечитати файл?
>>> a_file.read() ''
Так як ми все ще знаходимось в кінці файла, наступні виклики методу потоку read() просто повернуть порожній рядок.
>>> a_file.seek(0) 0
Метод seek() пененосить нас до вказаної в байтах позиції у файлі.
>>> a_file.read(16) 'Dive Into Python'
Метод read() приймає необов’язковий параметр - кількість символів для читання.
>>> a_file.read(1) ' '
Якщо хочете, можна навіть читати по одному символу за раз.
>>> a_file.read(1) '是' >>> a_file.tell() 20
16 + 1 + 1 = … 20?
Давайте спробуємо спочатку.
>>> a_file.seek(17) 17 >>> a_file.read(1) '是' >>> a_file.tell() 20
Переміщуємось на 17-тий байт. Читаємо один символ. Після цього ми опиняємось на 20-тому байті.
Ви бачите? Методи seek() та tell() рахують байти, але тому що ми відкрили файл як текст, метод read() рахує символи. Китайські ієрогліфи потребують кількох байт для того щоб бути закодованими в UTF-8. Кожен з латинських символів потребує лише одного байта в файлі, тому це може переконати вас що seek() та read() рахують одне й те ж, але це вірно лише для деяких символів.
Але зачекайте, буде ще гірше!
>>> a_file.seek(18) 18 >>> a_file.read(1) Traceback (most recent call last): File "<pyshell#12>", line 1, in <module> a_file.read(1) File "C:\Python31\lib\codecs.py", line 300, in decode (result, consumed) = self._buffer_decode(data, self.errors, final) UnicodeDecodeError: 'utf8' codec can't decode byte 0x98 in position 0: unexpected code byte
Перемістіться на 18-тий байт та спробуйте прочитати один символ. Чому це не працює? Тому що на 18-тому байті немає символа. Найближчий символ починається на 17-тому байті та простягається на три байти. Спроба почати читати символ з його середини призводить до UnicodeDecodeError.
Закривання файлів Відкриті файли споживають системні ресурси, та залежно від режиму, інші програми можуть не отримати до них доступу. Тому важливо закривати файли, як тільки ви закінчите роботу з ними.
>>> a_file.close()
Потоковий об’єкт a_file досі існує, виклик методу close() не знищує його. Але цей об’єкт не є надто корисним.
>>> a_file.read() Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: I/O operation on closed file.
Ви не можете читати закритий файл, це призводить до винятку.
>>> a_file.seek(0) Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: I/O operation on closed file.
Не можна переміщуватись по закритому файла.
>>> a_file.tell() Traceback (most recent call last): File "<input>", line 1, in <module> ValueError: I/O operation on closed file.
В закритому файлі немає позиції, тому tell() теж не працює.
>>> a_file.close()
Хоча й дещо несподівано, але виклик методу close() над потоковим об’єктом чий файл вже був закритий не створює винятку. Просто нічого не відбувається.
>>> a_file.closed True
Закритий потоковий об’єкт має один корисний атрибут: closed який підтверджує факт закривання файла.
Автоматичне закривання файлів
Потокові об’єкти мають явний метод close(), але що станеться якщо у вашому коді є помилка, і програма завалюється ще до того як ви викличите close()? Цей файл теоретично може залитатись відкритим набагато довше ніж необхідно. Поки ви тестуєте програму на своєму комп’ютері, це не така вже й проблема. Але на працюючому сервері - може бути проблемою.
Python 2 мав для розв’язок цієї проблеми: блок try..finally. Таке все ще працює в Python 3 і ви могли бачити це в коді інших людей, чи в старому коді який перенесли на Python 3. Але починаючи з Python 2.6 було введено більш акуратний спосіб, якому потрібно віддавати перевагу - конструкція with
with open('examples/chinese.txt', encoding='utf-8') as a_file: a_file.seek(17) a_character = a_file.read(1) print(a_character)
Цей код викликає open(), але ніде не видно a_file.close(). Слово with починає блок, аналогічно до подібного в конструкції if чи for. Всередині цього блоку коду ви можете використовувати змінну a_file в якості потокового об’єкта що повертається функцією open(). Звичайні методи потокового об’єкта все ще доступні — seek(), read(), будь-що що вам потрібно. Коли блок with закінучєеться, Python викликає a_file.close() автоматично.
Ось що цікаво: незважаючи на те як, чи коли ви вийдете з блоку with, Python закриє файл. Навіть якщо ви "вийдете" з блоку за допомогою необробленого винятку. Так, це правда, навіть якщо ваш код згенерує виняток, і вся програма завершить роботу, файл все одно буде закритим. Гарантовано.
Немає нічого специфічного для файлів у конструкції with, це просто загальний спосіб створення контекстів виконання та повідомлення об’єктам що вони входять та виходять з цього контексту. Якщо наш об’єкт є потоковим об’єктом, тоді він виконує корисні для файла речі (такі як його автоматичне закривання). Але така поведінка описана в потоковому об’єкті, а не в самій конструкції with. Існує багато інших способів використовувати менеджери контексту які жодним чином не пов’язані з файлами. Ви навіть можете створити свій власний, як буде показано далі в цьому розділі.
Читання даних по рядку за раз Рядок тексту - це те про що ви подумали - ви набираєте кілька слів, натискаєте на опиняєтесь на новому рядку. Рядок тексту це послідовність символів, обмежена... чим саме? Ну, це складно сказати, тому що текстові файли можуть використовувати різні символи для позначення кінця рядка. Кожна операційна система має свої власні правила. Одні використовують символ повернення каретки, інші використовують символ нового рядка, а деякі використовують обидва символи для кінця кожного рядка.
Тепер зітхніть з полегшенням, тому що Python автоматично обробляє закінчення рядків. Якщо ви скажете "Я хочу читати цей текстовий файл порядково", Python з’ясує який вид закінчень рядків використовується, і все буде Просто Працювати.
Отож, як це зробити? Прочитати файл по рядку за раз, це дуже просто і дуже красиво.
line_number = 0 with open('examples/favorite-people.txt', encoding='utf-8') as a_file: ① for a_line in a_file: ② line_number += 1 print('{:>4} {}'.format(line_number, a_line.rstrip())) ③
① Використовуючи конструкцію with ми безпечно відкриваємо файл, і покладаємо обов’язок його закривання на Python.