Произвольный доступ к данным в файлах

proizvolnyj dostup k dannym v fajlah Инструменты для работы с файлами и каталогами

При работе с двоичными файлами часто также применяется операция произвольного доступа. Ранее упоминалось, что добавление символа + в строку режима открытия файла позволяет выполнять обе операции, чтения и записи. Этот режим обычно используется вместе с методом seek объектов файлов, позволяющим выполнять чтение/запись произвольных участков файла. Такие гибкие режимы обработки файлов позволяют читать байты из одного места, записывать в другое и так далее. При объединении этих режимов с двоичным режимом появляется возможность извлекать и изменять произвольные байты в файле.

Выше для перехода в начало файла вместо операций закрытия файла и повторного его открытия использовался метод seek. Как уже упоминалось, операции чтения и записи всегда выполняются в текущей позиции в файле. При открытии файлов текущая позиция обычно устанавливается в смещение 0 от начала файла и перемещается вперед по мере чтения/записи данных. Метод seek позволяет переместить текущую позицию для следующей операции чтения/записи в другое место, для чего ему достаточно передать величину смещения в байтах.

Метод seek в языке Python принимает также второй необязательный аргумент, который определяет физический смысл первого аргумента и может принимать одно из трех значений: 0 — абсолютная позиция в файле (по умолчанию), 1 — смещение относительно текущей позиции и 2 — смещение относительно конца файла. Когда методу seek передается только аргумент смещения 0, это соответствует операции перемотки файла в начало (rewind): текущая позиция перемещается в начало файла. Вообще, метод seek поддерживает произвольный доступ на уровне смещения в байтах. Используя в качестве множителя размер записи в двоичном файле, можно организовать доступ к записям по их относительным позициям.

Метод seek можно использовать и без спецификатора + в строке режима для функции open (например, чтобы просто обеспечить произвольное чтение данных), но наибольшая гибкость достигается при работе с файлами, открытыми для чтения и записи. Возможность произвольного доступа поддерживается и для файлов, открытых в текстовом режиме. Но выполняющиеся в текстовом режиме операции кодирования/деко- дирования Юникода и преобразования символов конца строки сильно осложняют вычисление абсолютных смещений в байтах и длин, необходимых методам позиционирования и чтения, — представление ваших данных может значительно измениться при сохранении в файл. Кроме того, применение текстового режима может также ухудшить переносимость данных между платформами, где по умолчанию используются различные кодировки, если только вы не предполагаете всегда явно указывать кодировку файлов. Метод seek лучше подходит для работы с двоичными файлами; исключение составляет простой некодируемый текст ASCII, в котором отсутствуют символы конца строки.

Для демонстрации создадим файл в режиме w+b(эквивалент режима wb+”) и запишем в него некоторые данные — этот режим позволяет читать из файла и писать в него и создает новый пустой файл, если он существовал прежде (это относится ко всем режимам w”). После записи данных мы вернемся в начало файла и прочитаем его содержимое (несколько целочисленных значений, возвращаемых вызовами методов в этом примере, было опущено ради экономии места):

>>> records = [bytes([char] * 8) for char in b’spam’]

>>> records

[b’ssssssss’, b’pppppppp’, b’aaaaaaaa’, b’mmmmmmmm’]

подпись: >>> file = open(‘random.bin’, ‘w+b’)
>>> for rec in records:
... size = file.write(rec)
>>> file.flush()
# >> pos = file.seek(0)
# >> print(file.read())
b’ssssssssppppppppaaaaaaaammmmmmmm’
подпись: # записать четыре записи
# bytes означает двоичный режим
# прочитать файл целиком



Теперь повторно откроем файл в режиме r+b— он также позволяет читать из файла и писать в него, но не очищает файл при открытии. На этот раз мы будем выполнять позиционирование и чтение с учетом размеров элементов данных («записей»), чтобы показать возможность получения и изменения записей в произвольном порядке:

c:\temp> python

>>> file = open(‘random.bin’, ‘r+b’)

>   >> print(file.read()) # прочитать файл целиком

b’ssssssssppppppppaaaaaaaammmmmmmm’

>   >> record = b’X’ * 8

>   >> file.seek(0) # изменить первую запись

>   >> file.write(record)

>   >> file.seek(len(record) * 2) # изменить третью запись

>   >> file.write(b’Y’ * 8)

>   >> file.seek(8)

>   >> file.read(len(record)) # извлечь вторую запись

b’pppppppp’

>   >> file.read(len(record)) # извлечь следующую (третью) запись

b’YYYYYYYY’

>   >> file.seek(0) # прочитать файл целиком

>>> file.read()

b’XXXXXXXXppppppppYYYYYYYYmmmmmmmm’

подпись: c:\temp> type random.bin
xxxxxxxxppppppppyyyyyyyymmmmmmmm
подпись: # посмотреть файл за пределами python

Наконец, имейте в виду, что метод seek можно использовать, даже если файл открыт только для чтения. Следующий пример демонстрирует возможность чтения произвольных записей фиксированной длины. Обратите внимание, что при этом используется текстовый режим r”: поскольку данные представляют собой простой текст ASCII, где каждый символ представлен одним байтом, и текст не содержит символов конца строки, на данной платформе текстовый и двоичный режимы действуют одинаково:

c:\temp> python

>>> file = open(‘random.bin’, ‘r’) # текстовый режим можно использовать, если

# не выполняется кодирование и отсутствуют

# символы конца строки

>>> reclen = 8

>>> file.seek(reclen * 3) # извлечь четвертую запись

>>> file.read(reclen)

mmmmmmmm

>>> file.seek(reclen * 1) # извлечь вторую запись

>>> file.read(reclen)

‘pppppppp’

 

>  >> file = open(‘random.bin’, ‘rb’) # в данном случае двоичный режим действует # точно так же

>  >> file.seek(reclen * 2) # извлечь третью запись

>  >> file.read(reclen) # вернет строку байтов

bYYYYYYYY

Но в общем случае текстовый режим не следует использовать, если вам требуется произвольный доступ к записям (за исключением файлов с простым некодируемым текстом, подобным ASCII, не содержащим символов конца строки). Символы конца строки могут преобразовываться в Windows, а применение кодировок Юникода может вносить различные искажения — оба эти преобразования существенно осложняют возможность позиционирования по абсолютному смещению. Например, в следующем фрагменте соответствие между строкой Python и ее кодированным представлением в файле нарушается сразу же за первым неASCII символом:

>  >> data = sp\xe4m # данные в сценарии

>  >> data, len(data) # 4 символа Юникода,

(‘spam’, 4) # 1 символ не-ASCII

>  >> data.encode(‘utf8’), len(data.encode(‘utf8’)) # байты для записи в файл (b’sp\xc3\xa4m’, 5)

>  >> f = open(‘test’, mode=’w+’, encoding=’utf8’) # текст. режим, кодирование

>  >> f.write(data)

>  >> f.flush()

>  >> f.seek(0); f.read(1) # работает для байтов ascii

‘s’

>  >> f.seek(2); f.read(1) # 2-байтовый не-ASCII

a

>  >> data[3] # а в смещении 3 — не m!

‘m’

>  >> f.seek(3); f.read(1)

UnicodeDecodeError: ‘utf8’ codec can’t decode byte 0xa4 in position 0: unexpected code byte

(UnicodeDecodeError: кодек utf8’ не может преобразовать байт 0xa4 в позиции 0: неопознанный код)

Как видите, режимы открытия файлов в Python обеспечивают необходимую гибкость при работе с файлами в программах. А модуль os предлагает еще более широкие возможности для обработки файлов, которые представлены в следующем разделе.

Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, I том, 2011

Оцените статью
Секреты программирования
Добавить комментарий