====== Работа с файлами ======
В зависимости от того, с какими опциями открыт файл в 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.
* Будьте осторожны с записью и перезаписью файлов.