====== Работа с файлами ====== В зависимости от того, с какими опциями открыт файл в Python он может быть или текстовым (из него будут читаться строки ''str'') или двоичным (из него будут читаться байтовые строки ''bytes''). ===== Текстовые файлы ===== ==== Структура текстового файла ==== Hello world! Привет мир!
==== Машинное представление ==== 00000000 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a d0 9f d1 |Hello world!....| 00000010 80 d0 b8 d0 b2 d0 b5 d1 82 20 d0 bc d0 b8 d1 80 |......... ......| 00000020 21 0a |!.|
==== Пара слов о кодировках ==== Кодировки бывают: * Однобайтовые (кодовые страницы, например, CP1251): один байт соответствует одному символу. Диапазон 0-127 одинаков для всех кодовых страниц и включает символы латинского алфавита, знакам препинания, цифры, основные знаки математических действий некоторые непечатные символы. Диапазон 127-255 свой для каждой кодировки. * Многобайтовые (Unicode) * С фиксированной шириной (например, UTF-32): 2, 4 или 8 байт на каждый символ. * С плавающей шириной (например, UTF-8, UTF-16): количество байтов на символ, зависит от символа.
==== Популярные кодировки ==== * ''ascii'' — текстовые файлы IBM, только латиница. * ''cp1251'' — текстовые файлы в русской локализации Windows. * ''cp866'' — текстовые файлы в русской локализации DOS. * ''utf8'' — стандарт для современных Unix-like систем. Кодировка текстовых файлов в Python по умолчанию. Кодировка исходного кода на Python по умолчанию.
==== Популярные кодировки ==== Еще Windows, Unix и Mac отличаются символами конца строки в текстовых файлах: * Windows: ''\n\r'' * Unix: ''\n'' * Mac: ''\r''
===== Работа с текстовыми файлами ===== ==== Открытие и закрытие файла ==== # Открытие на чтение (текст) fobj = open("test.txt") # Открытие на чтение в заданной кодировке (текст) fobj = open("test.txt", encoding="utf8") fobj = open("test.txt", encoding="utf8", newline="\n") # Работа с файлом # Закрытие fobj.close() Преимущества: простота. Проблема: если при работе с файлом возникнет исключение, то файл может останется незакрытым до завершения работы программы.
==== Работа с файлом через with ==== with open("test.txt") as fobj: # Работа с файлом Преимущества: если при работе с файлом возникнет исключение, то он все равно будет закрыт. Проблема: рост уровня вложенности.
==== Чтение файла ==== fobj = open("test.txt") # Прочесть 5 байт s = fobj.read(5) # s == 'Hello' # Прочесть еще 5 байт s = fobj.read(5) # s == ' worl' # Прочесть файл до конца s = fobj.read() # s == 'd!\nПривет мир!\n'
==== Чтение в список строк ==== fobj = open("test.txt") # Прочесть файл sa = fobj.readlines() # sa == ['Hello world!\n','Привет мир!\n'] fobj.close() Преимущества: Можно обращаться к отдельным строкам. Недостатки: Файл приходится целиком загружать в память.
==== Чтение файла построчно ==== fobj = open("test.txt") for line in fobj: # line — текущая строка # line[-1] == '\n' print(line) fobj.close() Преимущества: В каждый момент времени в память загружена только одна строка. Недостатки: Некоторые алгоритмы обработки файлов трудно реализовать.
==== Перемещение по файлу ==== По любому файлу (текстовому или двоичному) можно перемещаться по номеру байта. (осторожнее с Unicode, если номер окажется посередине многобайтового символа — будет ошибка). fobj = open("test.txt") s = fobj.read() # s == 'Hello world!\nПривет мир!\n' #Получить текущее положение в файле (в байтах) ps = fobj.tell() #ps == 34 #Перейти на заданное положение в файле (в байтах) fobj.seek(13) s = fobj.read(5) # s == 'Приве' # Перейти в начало fobj.seek(0) s = fobj.read() # s == 'Hello world!\nПривет мир!\n' fobj.close()
==== Открытие файла на запись ==== По умолчанию файлы открываются только на чтение. Для записи в файл его нужно открыть с соответствующей опцией (второй аргумент ''open''), которая по традиции обозначается буквой. Если файл не существует, он будет создан, если существует, то возможны варианты. ^ Опция ^ Режим ^ Текущая позиция чтения/записи ^ | пусто или ''r'' | Только чтение | начало | | ''w'' | Запись, файл обрезается до пустого | начало | | ''r+'' | Чтение и запись, файл не обрезается | начало | | ''a'' | Запись, файл не обрезается | конец | | ''a+'' | Чтение и запись, файл не обрезается | конец |
==== Запись в файл ==== fobj = open("test.txt", "w") wbc = fobj.write("Hello world!\n") # wbc == 13 fobj.close() fobj = open("test.txt", "r+") content = fobj.read() wbc = fobj.write("Hello world!\n") # wbc == 13 wt = fobj.tell() # wt == 26 fobj.close()
===== Двоичные файлы ===== Работа с двоичным файлом аналогична работе с текстовым, однако меняются опции открытия файла. И читать/писать придется объекты типа ''bytes'', а не ''str''. ^ Опция ^ Режим ^ Текущая позиция чтения/записи ^ | ''b'' или ''rb'' | Только чтение | начало | | ''wb'' | Запись, файл обрезается до пустого | начало | | ''rb+'' | Чтение и запись, файл не обрезается | начало | | ''ab'' | Запись, файл не обрезается | конец | | ''ab+'' | Чтение и запись, файл не обрезается | конец |
==== Чтение и запись ==== fobj = open("test.txt", "ab+") ds = "Привет мир!\n" dbytes = ds.encode('utf8') # dbytes == b'\xd0\x9f\xd1\x80\xd0... wbc = fobj.write(dbytes) # wbc == 21 fobj.seek(0) dbs = fobj.read(24) #dbs == b'Hello world!\nHello world!\n\xd0\x9f\xd1... len(dbs) == 24 # True fobj.close()
==== Чтение и запись Python объектов ==== Самый простой способ сохранить структурированные данные в Python — воспользоваться модулем ''pickle'' стандартной библиотеки. import pickle sdata = {'keyA': [1, 2, 3], 'keyB': ("строка", b"bytes"), 'keyC': 'Тест' } fobj = open('data.pickle', 'wb') pickle.dump(sdata, fobj) fobj.close() fobj.open('data.pickle', 'rb') tdata = pickle.load(fobj) fobj.close() print(tdata) # {'keyA': [1, 2, 3], 'keyB': ('строка', b'bytes'), 'keyC': 'Тест'} Достоинства: Простота. Сохраняет практически любой Python объект. Недостатки: Только для Python. Небезопасно с точки зрения обмена данными.
==== Чтение и запись числовых массивов ==== Для работы с массивами чисел в заданном машинном формате в Python есть класс ''array''. import array ar = array.array('f', [2.0, 4.5, 3.3]) fobj = open('array.dat', 'wb') ar.tofile(fobj) fobj.close() ar2 = array.array('f') fobj = open('array.dat', 'rb') ar2.fromfile(fobj, 3) fobj.close() print(ar2) #array('f', [2.0, 4.5, 3.299999952316284])
=== Некоторые форматы === ^ Формат ^ C-тип ^ Python-тип ^ Размер элемента в байтах ^ | ''b'' | signed char | ''int'' | 1 | | ''B'' | unsigned char | ''int'' | 1 | | ''l'' | signed long | ''int'' | 4 | | ''L'' | unsigned long | ''int'' | 4 | | ''f'' | float | ''float'' | 4 | | ''d'' | double | ''float'' | 8 | [[https://docs.python.org/3.6/library/array.html|Подробная таблица форматов]]. Достоинства: Простота. Переносимость. Недостатки: Можно сохранять только массивы чисел.
==== Чтение и запись С-структур ==== Для работы со структурами данных C в Python есть модуль ''struct'' и его чуть более функциональный товарищ ''rawutil''pip. import struct st = struct.Struct('10sIIxBB') data = st.pack(b"Hello",2,3,19,19) # data == b'Hello\x00\x00\x00\x00\x00... fobj = open('struct.dat', 'wb') fobj.write(data) fobj.close() fobj = open('struct.dat', 'rb') td = fobj.read() fobj.close() values = st.unpack(td) print(values) # (b'Hello\x00\x00\x00\x00\x00', 2, 3, 19, 19) Если структура данных сложная, но есть ее описание на С, то можно воспользоваться модулем ''cffi''pip.
=== Некоторые форматы ''struct'' === Управление порядком байт ''<'' — little-endian, ''>'' — big-endian. ^ Формат ^ C-тип ^ Python-тип ^ Размер элемента в байтах ^ | ''x'' | — | — | 1 | | ''b'' | signed char | ''int'' | 1 | | ''B'' | unsigned char | ''int'' | 1 | | ''i'' | int | ''int'' | 4 | | ''I'' | unsigned int | ''int'' | 4 | | ''f'' | float | ''float'' | 4 | | ''d'' | double | ''float'' | 8 | | ''s'' | char[] | ''bytes'' | 1 | [[https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment|Подробная таблица форматов]]. Достоинства: Переносимость. Недостатки: Нужно вручную описывать формат структур, думать о порядке байт и выравнивании.
===== Файлы отображенные в память ===== Есть возможность работать с файлом, как с массивом байтов не загружая его в память полностью. Для этого используется отображения файла в память. import mmap fobj = open("hello.txt", "r+b") # Создаем отображенный файл: # Первый аргумент - дескриптор заранее открытого файла # Второй - начальное положение в байтах (можно отобразить не весь файл) mapobj = mmap.mmap(fobj.fileno(), 0) # Работаем с файлом как с массивом байтов print(mapobj[:5]) # prints b"Hello" mapobj[6:] = b" world!\n" mapobj.close() Также файл отображенный на память поддерживает стандартные методы для работы с файлами, напрмер ''read'' и ''write''.
===== Псевдофайлы ===== С точки зрения Python файлы это просто объекты имеющие определенные методы. В Python есть классы объекты которых выглядят, как утки файлы, но файлами не являются. Например ''StringIO'' и ''BytesIO'' из модуля ''io'' стандартной библиотеки. import io fobj = io.StringIO() fobj.write('First line.\n') fobj.seek(5) s = fobj.read() # s == ' line.\n' as = fobj.getvalue() # as == 'First line.\n' fobj.close()
===== Сводная таблица методов файлов ===== ^ Метод ^ Действие ^ Возвращает ^ | ''read()'' | Читает файл от текущей позиции до конца | ''str''/''bytes'' | | ''read(n)'' | Читает ''n'' символов или байт | ''str''/''bytes'' | | ''readlines()'' | Читает текстовый файл от текущей позиции до конца, разбивает результат по ''os.linesep'' | ''str''/''bytes'' | | ''write(x)'' | Записывает в файл с текущей позиции ''str''/''bytes'', возвращает число записанных символов / байт | ''int'' | | ''tell()'' | Текущее положение в файле **всегда в байтах** | ''int'' | | ''seek(n)'' | Перемещается на позицию ''n'' в файле **всегда в байтах**, возвращает позицию на которую удалось переместиться | ''int'' | | ''flush()'' | Записывает сбрасывает буфер на диск | ''None'' | | ''close()'' | Закрывает файл | ''None'' |
===== Особые файлы ===== При запуске скрипта автоматически открываются 3 файла доступных в модуле ''sys'': ^ Файл ^ Тип ^ Смысл ^ | ''sys.stdout'' | ''w'' | вывод в консоль | | ''sys.stdout.buffer'' | ''wb'' | вывод в консоль | | ''sys.stderr'' | ''w'' | сообщения об ошибках | | ''sys.stderr.buffer'' | ''wb'' | сообщения об ошибках | | ''sys.stdin'' | ''r'' | ввод с консоли | | ''sys.stdin.buffer'' | ''rb'' | ввод с консоли | Такие операции как ''print'', ''input'' сводятся к работе с этими файлами.
===== Что еще нужно знать о файлах ===== * Как правило ''read()''/''write()'' — слишком низкий уровень. * По умолчанию запись в файл производится не в момент ''write'', а в момент, когда накопится достаточно данных для записи. Для того, чтобы выполнить запись немедленно есть метод ''flush()''. * Если файл больше десятка мегабайт, загружать его в память целиком — не очень хорошая идея. * При работе с двоичными файлами, помните, что есть byteorder. * Будьте осторожны с записью и перезаписью файлов.