Кодировки и работа с файлами
Байтовые структуры
Тип bytes — как str, только байты
- Ой, не совсем ☺:
- Часто прилетают при работе с функциями из Си, сетевыми протоколами и др. данными, за которые Питон отвечать не хочет
Тип bytearray
Изменяемый bytes
Bytearray нужен не очень часто:
- это по сути список однобайтовых целых без одного лишнего уровня косвенности (хранятся сами целые, а не объекты Python)
для сложных алгоритмов есть либо array, либо уж сразу NumPy
бНОПНЯ
То, что мы в ИТ называем «символами» — соответствие некоторого маркера термину — в действительности суть «знаки». Понятие «символа» слишком многозначно и нередко представляет собой нечто противоположное знаку: символ маркирует целый культурно-ассоциативный корпус, для каждого человека — а тем более в разных культурах — разный.
Как преобразовать из str в bytes и обратно?
- Кодировка
- соответствие некоторого числа конкретному символу
Кодировки бывают фиксированного размера и многобайтовые.
UNICODE — вообще не кодировка, а универсальный способ каталогизации знаков
⇒ Семейство кодировок UTF:
- исторически первые
- простые
по нескольку штук на каждый язык
- без указания кодировки нельзя понять, что написано:
1 >>> txt = "Вопрос" 2 >>> print(*map(hex, map(ord, txt))) 3 0x412 0x43e 0x43f 0x440 0x43e 0x441 4 >>> txt.encode() 5 b'\xd0\x92\xd0\xbe\xd0\xbf\xd1\x80\xd0\xbe\xd1\x81' 6 >>> locale.getdefaultlocale() 7 ('ru_RU', 'UTF-8') 8 >>> txt.encode('UTF-8') 9 b'\xd0\x92\xd0\xbe\xd0\xbf\xd1\x80\xd0\xbe\xd1\x81' 10 >>> txt.encode('WINDOWS-1251') 11 b'\xc2\xee\xef\xf0\xee\xf1' 12 >>> txt.encode('KOI8-R') 13 b'\xf7\xcf\xd0\xd2\xcf\xd3' 14 >>> txt.encode('WINDOWS-1251').decode('KOI8-R') 15 'бНОПНЯ' 16
- Лучше их никогда не использовать!
KOI8-R (точнее, ДКОИ, что ещё безудержнее) — это актуальный российский стандарт ГОСТ 19768-93
⇒ Лучше всегда использовать UTF-8. Остальные ещё хуже.
Представление строк внутри Python:
т. н. «питоний unicode» (двухбайтовый UTF-16)
- Если в строке все символы из ASCII, она хранится побайтово, а если нет — в «Unicode», но это абсолютно прозрачно, кроме размера:
На 2024 help(encodings) есть, оттуда ведёт битая ссылка на документацию
Не всякие кодировки полны:"Вопрос".encode("koi8-r").decode("latin3")
Устарело ли понятие однобайтовой кодировки? Нет! бНОПНЯ живёт и процветает в Windows, например.
Просто файлы
open(), r/w/a и т. п.
.read()/.write()/.readline()
файл как итератор, .readlines()
.seek(),.tell()
текстовые и двоичные: "b"/"t"
- Непременное перекодирование текстовых файлов из локальной кодировки в Unicode и обратно.
.seek() / .tell() не текстовых файлах
файл как контекстный менеджер, with, зачем нужно (автозакрытие, обработка возникших исключений)
Отдельная тема: «файлы в операционной системе» — слишком много и не про язык, не будем туда ходить.
Но если видите sys.path — скорее всего пример устарел, сейчас модно pathlib (кроссплатформенно и более высокоуровнево)
1 >>> from pathlib import Path 2 >>> f = Path("~/o.text") 3 >>> f.expanduser() 4 PosixPath('/home/george/o.text' 5 >>> f.name, str(f) 6 ('o.text', '~/o.text') 7 >>> f.expanduser().parent 8 PosixPath('/home/george') 9 >>> f.parent / "src" / "file.py" 10 PosixPath('~/src/file.py') 11 >>> f.with_suffix(".binary") 12 PosixPath('~/o.binary') 13 >>> f.write_text("Кря!") 14 4 15 >>> f.read_bytes() 16 b'\xd0\x9a\xd1\x80\xd1\x8f!'
- и т. д.
Стандартный ввод-вывод
Как обычно: sys.stdin, sys.stdout и sys.stderr — это файлы стандартного ввода, вывода и вывода ошибок соответственно
К ним можно применять файловые операции (даже .seek(), но это только если соответствующий файл — это файл, а не терминал или конвейер (.seekable())
Например, sys.stdout.write("что_то_там") запишет что_то_там на стандартный вывод (не добавляя перевод строки, разумеется)
Это текстовые файлы
Двоичные аналоги — sys.stdin.buffer, sys.stdout.buffer и sys.stderr.buffer
Например, можно написать так: b = sys.stdin.buffer.readline(), при этом в b окажется объект типа bytes
Файловые объекты
Более высокий уровень абстракции — io (в действительности более низкий)
Типизированные файлы
В «просто файлы» записываются только строки или байты. А если надо записать float, причём не в виде строки?
Для начала вопрос: а в виде чего?
Сериализация
Чтение и запись объектов Python
pickle.dumps(obj) / pickle.dump(obj, file)
pickle.loads(bytes_object) / pickle.load(file)
Пример:
1 >>> import pickle
2 >>> pickle.dumps(0x14131211)
3 b'\x80\x04\x95\x06\x00\x00\x00\x00\x00\x00\x00J\x11\x12\x13\x14.'
4 >>> pickle.dumps(0x14131211)[-5:]
5 b'\x11\x12\x13\x14.'
6 >>> du = pickle.dumps(123.123e20)
7 >>> du
8 b'\x80\x04\x95\n\x00\x00\x00\x00\x00\x00\x00GD\x84\xdb\x9b\xe5\x05\x1cP.'
9 >>> ud = pickle.loads(du)
10 >>> ud
11 1.23123e+22
12 >>> F = open("serialized", "bw")
13 >>> pickle.dump(100500, F)
14 >>> pickle.dump([1, "WER", None], F)
15 >>> pickle.dump(b"QWWER", F)
16 >>> F.close()
17 >>> F = open("serialized", "br")
18 >>> pickle.load(F)
19 100500
20 >>> pickle.load(F)
21 [1, 'WER', None]
22 >>> pickle.load(F)
23 b'QWWER'
24
Сериализация экземпляра класса:
сериализация / десериализация основана на интроспекции экземпляра
- ⇒ поля класса и методы не сериализуются
⇒ при десериализации обычно не вызывается .__init__()
вызывается datamodel.html#object.__new__ соответствующего класса
восстанавливаетя .__dict__ и/или .__slots__
- ⇒ сам класс должен присутствовать в пространстве имён
- Впрочем, сам класс никто не мешает тоже сериализовать!
1 >>> class C: 2 ... CField = 100500 3 ... def __init__(self, val): 4 ... print("Truly create") 5 ... self.val = val 6 >>> c = C(123) 7 Truly create 8 >>> c.loc = "QWE" 9 >>> s = pickle.dumps(c) 10 >>> s 11 b'\x80\x04\x95.\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x01C\x94\x93\x94)\x81\x94}\x94(\x8c\x03val\x94K{\x8c\x03loc\x94\x8c\x03QWE\x94ub.' 12 >>> d = pickle.loads(s) 13 >>> d.CField 14 100500 15 >>> C.CField=42 16 >>> d.CField 17 42 18 >>> d.val 19 123 20 >>> d.loc 21 'QWE'
- Соответствие типов не проверяется (только имя). Если процедура десериализации не перегружена, подойдёт даже пустой класс:
небезопасно, т. к. потенциально можно исполнить произвольный код, напиханный злоумышленником
Структуры типа Си
Что мешает записать / считать представление объекта в памяти — это и будет в точности его контент?
- Правильный ответ: всё можно, «но есть нюансы» ☺
- Остроконечники и тупоконечники (порядок байтов в слове), sys.byteorder
- Выравнивание
Простой пример запаковки произвольных данных:
\x94\x88\x01\x00 → 100500
d} → 32100 (0x7d64)
\xc8 → это 200
\x00 → выравнивание до чётного адреса (на некоторых архитектурах — до адреса, кратного размеру целого)
\xfa\xff\xff\xff → -6
Пример: заголовок PNG (возможно, не успеем)
1 import struct
2 import zlib
3 import sys
4
5 HEADER = "8B"
6 CHUNK = "!I4s"
7 CRC = "!I"
8 IHDR = "!IIBBBBB"
9
10 def readpack(fmt, fle):
11 return struct.unpack(fmt, fle.read(struct.calcsize(fmt)))
12
13 payload = b''
14 with open(sys.argv[1], "br") as f:
15 png = readpack(HEADER, f)
16 print(*map(hex, png))
17 while (chunk := readpack(CHUNK, f))[1] != b"IEND":
18 print(*chunk)
19 data = f.read(chunk[0])
20 crc = readpack(CRC, f)
21 if chunk[1] == b"IHDR":
22 w, h, bpp, col, comp, filt, i = struct.unpack(IHDR, data)
23 print(f"{w}×{h}, {bpp=}, {col=}, {comp=}, {filt=}, {i=}")
24 elif chunk[1] == b"IDAT":
25 payload += data
26
27 print(len(payload), w, h, w*h)
28 payload = zlib.decompress(payload)
29 print(len(payload), w, h, w*h)
Базы данных и dict-like итерфейс
- Идея: интерфейс словаря (ключ:значение) + быстрый поиск под капотом
1 >>> import dbm 2 >>> F = dbm.open("data.db", "c") 3 >>> F["qwe"] = "rty" 4 >>> F["asd"] = "zxcv" 5 >>> F["qerw"] = "werw" 6 >>> F["123"] = "123" 7 >>> 8 >>> F["asd"] 9 b'zxcv' 10 >>> F[b"asd"] 11 b'zxcv' 12 >>> F["Ы"] = "Ы" 13 >>> F["Ы"] 14 b'\xd0\xab' 15 >>> F.close() 16 >>> F = dbm.open("data.db", "r") 17 >>> F["qerw"] 18 b'werw' 19 >>> k = F.firstkey() 20 >>> k 21 b'\xd0\xab' 22 >>> while k: 23 ... print(F[k]) 24 ... k = F.nextkey(k) 25 ... 26 b'\xd0\xab' 27 b'rty' 28 b'zxcv' 29 b'werw' 30 b'123' 31
Файлы с известной структурой
- Тысячи их, часть поддерживают файловый протокол, часть — нет
- …
Д/З
Ввод для некоторых задач нельзя сделать с клавиатуры — там могут быть произвольные байты. Тем не менее, в программе можно воспользоваться чтением из sys.stdin.buffer, а тестировать так:
… python3 программа.py < нетекстовый_файл
Собственно, задание:
Прощёлкать примеры с файлами в Tutorial, а также примеры по pickle и struct
EJudge: TarFile 'Размер архива'
Написать программу, которой на стандартный ввод подаётся tar-архив в виде шестнадцатеричного дампа (последовательность шестнадцатеричных цифр, возможно, разделённых пробелами и переводами строки), а на выходе она показывает количество и суммарный объём хранящихся в нём файлов, если их распаковать.
1f8b0800000000000003edd64d6ac3301005e059 f714ba8134d2482abe42c92a2748529365c1690e 9013147a9ff60cce8de23f085da4c18b51e3f2be 8d0d3648f0347e664bea5c27c7385ed3cfeb8458 42483e4a6621c72e4a2013f5b746743cbc6f1a63 685fbf35fbfaf67bf79e2f14db57f535fa8093c8 9cfc53ce649cface08f9db9dfa1a77f317bee6df 9d8521ff84fc4b683fcf27e39d69bfdaeff34777 e7bd715c8550856456eb97a7bfde20a892c7eaff 10328ffd2fe8ff12c446f513302fffa1ff33e3ff af883effadf21a33fa7fcabffb0078f47f0937fa df7325cfe8ffff4fec467d8d79f33ff47f7698ff 227e99ff88f90700000000000000000058ae0bcd cedba200280000
99 4
EJudge: BmpParser 'Анализ BMP'
Написать программу, разбирающую некоторые заголовки BMP-файла. Ограничения на структуру следующие:
- Рассматриваются только первые два заголовка: «Bitmap file header» и «DIB header»
- Необязательные секции отсутствуют или не влияют на разбор
- Исторический формат «DIB header» — любой (отличаются размерами, всего 8 штук)
Если в заголовке указан тип компрессии и размер картинки, этот размер даётся для распакованной картинки (то есть кратен ширине и высоте с учётом глубины цветности)
Если ширина картинки в байтах не кратна 4, в конец каждой строки пикселей дописывается недостающее число битов — это влияет на общий размер картинки
- Также допустимо, чтобы указанный в заголовке размер картинки
- был равен 0 (тогда он вычисляется из ширины высоты и глубины цвета)
- был на 2 больше вычисленного размера (я не разобрался, в каких случаях это так)
- Других заполнителей и выравнивания нет
Программа должна проверять, соответствуют ли входные данные () структуре BMP-файла, и выводить либо первое найденное несоответствие, либо информацию о файле:
Если в начале файла нет сигнатуры "BM", выводится "Not a Windows BMP"
Если поле «размер BMP-файла» не равно объёмы введённых данных, выводится "Incorrect size"
Если размер DIB header не равен одному из известных, выводится "Incorrect header size"
Если заданный размер картинки не равен вычисленному (с учётом возможного +2 и нулевого значения), выводится "Incorrect image size"
- В противном случае выводятся абсолютные значения ширины, высоты и глубины цветности картинки, а также метод упаковки (если не задан, то 0) и размер заполнителя (0 или 2)
…содержимое weasel2.4c.bmp…
82 73 4 0 0
EJudge: EncPairs 'Патриоты Зимбабве'
Некоторый текст на русском языке в однобайтовой кодировке, содержащий слово «Зимбабве», перекодировали в другую однобайтовую кодировку. Однако исходная и целевая кодировки при этом выбирались случайно — возможно, они не совпадали с кодировкой текста. Ошибок при перекодировке не произошло. Написать программу, которой на вход подаётся результат такой перекодировка, а на выходе должен получиться исходный текст. Доступные кодировки: KOI8-R, CP1251, MACCYRILLIC, CP866, ISO-8859-5, CP855. В примере приведён не сам входной файл, а его дамп.
00000000 bc 9f 96 f6 f0 90 e1 e0 20 80 96 84 20 f3 e0 f0 |........ ... ...| 00000010 90 e5 2c 20 84 e0 e6 e5 e6 e0 e1 93 e5 96 20 e3 |.., .......... .| 00000020 e2 93 82 20 e3 e0 90 96 9f e5 e0 94 82 83 e2 f2 |... ............| 00000030 20 c8 9f c7 92 96 f0 f0 c7 e1 20 e5 20 f2 c7 f6 | ......... . ...| 00000040 dc e6 e5 96 20 0a f4 20 c7 80 e6 96 83 e0 9f c7 |.... .. ........| 00000050 f6 83 c7 e3 f1 20 e5 20 e3 c7 f0 f4 c7 e1 f0 f4 |..... . ........| 00000060 c7 e3 f1 20 f0 e5 90 f1 2c 20 e1 c7 84 9f e0 f0 |... ...., ......| 00000070 90 e0 9c 90 20 84 e0 20 90 e5 c8 e5 f3 83 e2 f2 |.... .. ........| 00000080 20 f6 e1 c7 9f 83 e5 f4 c7 e1 2c 20 0a b8 e5 e3 | ........., ....| 00000090 80 e0 80 e1 96 20 f6 e1 c7 9f 83 e5 f4 e0 20 e5 |..... ........ .| 000000a0 84 e1 9f e0 e6 e0 dc 20 80 e2 90 e5 96 2e 20 b7 |....... ...... .| 000000b0 c7 2d f2 e0 e3 f0 f4 e5 20 9c 9f c7 f6 f0 90 e1 |.-...... .......| 000000c0 f1 9c e6 e0 dc 20 e0 83 e0 9f f2 e5 dc 20 0a f0 |..... ....... ..| 000000d0 90 9f c7 96 83 e5 dc 20 80 f1 f6 96 90 20 c8 9f |....... ..... ..| 000000e0 c7 f6 c7 94 e4 e0 90 82 20 c8 96 90 82 20 c7 20 |........ .... . | 000000f0 93 e0 c8 f4 96 20 90 9f e0 83 f0 91 c7 9f e3 e0 |..... ..........| 00000100 92 e5 e5 2e 20 be 84 83 e0 96 90 20 c7 20 c8 c7 |.... ...... . ..| 00000110 f0 94 96 f6 83 96 85 20 0a e5 f6 96 e0 94 e5 84 |....... ........| 00000120 e0 92 e5 e5 20 f0 20 c8 c7 84 83 e0 83 e5 dc e3 |.... . .........| 00000130 e5 20 f4 e0 c8 e5 90 e0 94 e5 84 e3 20 f0 20 f4 |. .......... . .| 00000140 c7 94 82 92 c7 e3 2e 20 bf c7 94 f0 90 e0 dc 20 |....... ....... | 00000150 e5 20 90 c7 83 f4 e0 dc 20 0a 9f e2 80 e0 2c 20 |. ...... ....., | 00000160 c7 90 9f e0 e4 e0 9c e6 e0 dc 20 80 96 84 c7 80 |.......... .....| 00000170 9f e0 84 e5 dc 20 c8 9f e5 84 9f e0 f4 c7 e3 20 |..... ......... | 00000180 83 e0 92 e5 c7 83 e0 94 82 83 c7 85 20 80 e0 83 |............ ...| 00000190 e5 2c 20 80 f1 f6 96 90 20 f0 c7 c7 90 e1 96 90 |., ..... .......| 000001a0 f0 90 e1 c7 e1 e0 90 82 20 0a e0 f4 90 e5 e1 83 |........ .......| 000001b0 c7 85 20 e5 20 90 9f 96 84 e1 c7 85 20 c7 93 e5 |.. . ....... ...| 000001c0 80 f4 96 2c 20 e1 e2 c8 e5 e1 93 e5 20 e5 20 9c |..., ....... . .| 000001d0 9f c7 f6 f0 90 e1 f1 dc 2e 20 be 90 c7 c8 e5 f3 |......... ......| 000001e0 96 f0 f4 e5 96 20 f0 f1 e6 83 c7 f0 90 e5 2c 20 |..... ........, | 000001f0 0a f0 f4 e0 84 e0 83 83 e2 96 20 c7 20 81 e0 94 |.......... . ...| 00000200 c7 81 96 83 c7 e1 c7 f6 c7 9f c7 f6 e0 f2 20 e5 |.............. .| 00000210 20 e1 9f 96 e3 96 83 e0 e3 e5 20 f0 20 f0 9f 96 | ......... . ...| 00000220 f6 f0 90 e1 c7 e3 20 e1 f4 94 9c f3 e0 9c e6 e5 |...... .........| 00000230 96 0a |..|
Средства без части, защищавшие мышь материальных процессов и ходящие к общенародному и московскому ситу, возрастают за типичных дворников, Зимбабве дворника извращая бытие. По-хамски юродствующая анархия строения будет продолжать петь о шапке трансформации. Узнает о последней идеализации с познаниями капитализм с кольцом. Толстая и тонкая рыба, отражающая безобразия призраком национальной бани, будет соответствовать активной и трезвой ошибке, выпивши и юродствуя. Утопические сущности, сказанные о галогеноводородах и временами с средством включающие