Работа с файлами
Оператор with
Заранее задать finally (например, закрыть открытый файл, даже если были исключения)
протокол контекстного менеджера
.__enter__() / .__exit__(exc_type, exc_val, exc_tb)
1 class CM: 2 def __init__(self, val = None): 3 self.val = val 4 5 def __enter__(self): 6 print(">>>") 7 8 def __exit__(self, *args): 9 print("<<<",*(c for c in args if c), sep="/", end=">>>\n") 10 return self.val 11 12 with CM(True) as f: 13 print("Working") 14 raise SyntaxError("шhат?") 15 16 print("Done")
- →
Working <<</<class 'SyntaxError'>/шhат?/<traceback object at 0x7f5fe5bac500>>>> Done
Если .__exit__() возвращает пустой объект (в примере можно передать конструктору False), исключения, возникшие при выходе из контекста, распространяются на уровень выше (в примере — не ловятся)
Далее см. contextlib.html
Байтовые структуры
Тип bytes — как str, только байты
- Ой, не совсем ☺:
- Часто прилетают пр работе с функциями из Си, сетевыми протоколами и др. данными, за которые Питон отвечать не хочет
- (в Python2 все такими было, кое-где может встретиться legacy)
Тип bytearray
Изменяемый bytes
Bytearray нужен не очень часто:
- это по сути список однобайтовых целых без одного линшего уровня косвенности (хранятся сами целые, а не объекты Python)
есть либо NumPy, либо array
бНОПНЯ
Как преобразовать из str в bytes и обратно?
- Понятие кодировки. Unicode, UTF* и прочее
Кодировка: соответствие некоторого числа конкретному символу (например, описанному в Unicode)
- Тысячи их
UTF-8: то ли один байт, то ли два (то ли три, то ли четыре)
- Однобайтные кодировки: без указания кодировки нельзя понять, что написано
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
- Представление строк внутри Python: т. н. «питоний unicode» (двухбайтовый UCS-16)
- Если в строке все символы из ASCII, она хранится побайтово, а если нет — в Unicode, но это абсолютно прозрачно, кроме размера:
Не всякие кодировки полны:"Вопрос".encode("koi8-r").decode("latin3")
Просто файлы
open(), r/w/a и т. п.
.read()/.write()/.readline()
файл как итератор, .readlines()
.seek(),.tell()
текстовые и двоичные: "b"/"t"
- Непременное перекодирование текстовых файлов из локальной кодировки в Unicode и обратно.
.seek() / .tell() не текстовых файлах
файл как контекстный менеджер, with, зачем нужно (автозакрытие, обработка возникших исключений)
Отдельная тема: файлы в операционной системе, os, pathlib и т. д. — слишком много и не про язык, не будем туда ходить.
Стандартный ввод-вывод
Как обычно, 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__()
вызывается .__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
Файлы с известной структурой
- Тысячи их, часть поддерживают файловый протокол, часть — нет
- …
Д/З
Предполагается, что стандартный ввод в тестах — это файлы. Если для решения нужен .seek(), вводить с клаиатуры не получится. Можно запускать программу из консоли примерно так:
… python3 программа.py < тестовый_файл
Собственно, задание:
Прощёлкать примеры с файлами в Tutorial, а также примеры по pickle и struct
EJudge: DeStruct 'Разбор пакета'
На вход подаётся содержимое некоторого пакета данных — строка в формате base85. Пакет состоит из заголовка и тела. Заголовок содержит последовательность ненулевых байтов, заканчивающуюся нулевым. Каждый байт заголовка — число 1, -1, 2, -2, 4, -4, 8 или -8 (других нет). Модуль этого числа описывает количество байтов в очередном поле записи. Тело состоит из нуля или более записей, определяемых в заголовке. Если число отрицательное, соответствующее поле — целое со знаком, если положительное — беззнаковое. Выравнивания между полями и между записями нет. Порядок байтов — «сетевой» (big endian). Вывести сумму всех полей пакета.
1pod3sdqp1V;v|??MM2idn8p=)rm(+um
5599084740
EJudge: LightBnopnya 'Случилась бНОПНЯ'
Текст процедуры на языке Рапира в кодировке koi8-r был несколько раз перекодирован, причём перекодировщику сообщали совершенно произвольную исходную и целевую однобайтную кодировку (например, из cp866 в latin_1). Результат перекодировали из последней целевой кодировки в UTF-8 . Восстановить предполагаемый текст процедуры.
Известно, что в процессе перекодирования не возникало ошибок
- Все буквы — заглавные русские; никаких других не-ASCII символов в тексте не было
Синтаксис внутри процедуры не соблюдается, но
процедура начинается на "ПРОЦ" и заканчивается на "КНЦ;"
внутри процедуры есть оператор "ВЫВОД: "
текущая формулировка задачи и набор тестов допускают неоднозначное декодирование. в 2022 году проблема лечится либо оптимальным декодированием (двойным вместо тройного), либо дополнительной проверкой на слово «ОСОВЕННОСТИ» — его не должно быть в ответе. TODO В будущем формулировку пересмотреть
Список допустимых кодировок: cp037 cp1006 cp1250 cp1251 cp1253 cp1254 cp1255 cp1256 cp1257 cp1258 cp437 cp720 cp737 cp775 cp850 cp852 cp855 cp864 cp866 cp869 cp874 cp875 hp_roman8 iso8859_10 iso8859_16 iso8859_4 iso8859_5 koi8_r latin_1 mac_croatian mac_greek mac_iceland mac_latin2
- Максимальное количество перекодирований — 3
- В процедуре не более 25 тысяч символов
ЪЫЩж ИЩЫЖРНЩ(); ЭвЭЩН: "ЦЩкОЮ ЫЖРЭУЮУн ЩЗЫЖРЩЦ ТЬХЩЭУн ХУмЧЩЬЮУ." НЩ ЩЮ ЭЬО 0-8-6+4/ЭЬОЦ; ЪЫЩж ОЬХУ 6()6=7+6()3:: ЭвЭЩН: "ЪЫУмОЦ 7." ЭвЭЩН: "ЭОЬд НЫТИ ЪЫЩОФЮЖ ЭЬОЦ ОЦТ ЪЫУСЩНУЮЬн?" ФЧж;
ПРОЦ ГОРАЗДО(); ВЫВОД: "МОЖЕТ РАЗВИТИЯ ОБРАЗОМ УСЛОВИЯ ЛИЧНОСТИ." ДО ОТ ВСЕ 0-8-6+4/ВСЕМ; ПРОЦ ЕСЛИ 6()6=7+6()3:: ВЫВОД: "ПРИЧЕМ 7." ВЫВОД: "ВЕСЬ ДРУГ ПРОЕКТА ВСЕМ ЕМУ ПРИХОДИТСЯ?" КНЦ;
EJudge: TarFile 'Размер архива'
Написать программу, которой на стандартный ввод подаётся tar-архив в виде шестнадцатеричного дампа (последовательность шестнадцатеричных цифр, возможно, разделённых пробелами и переводами строки), а на выходе она показывает количество и суммарный объём хранящихся в нём файлов, если их распаковать.
1f8b0800000000000003edd64d6ac3301005e059 f714ba8134d2482abe42c92a2748529365c1690e 9013147a9ff60cce8de23f085da4c18b51e3f2be 8d0d3648f0347e664bea5c27c7385ed3cfeb8458 42483e4a6621c72e4a2013f5b746743cbc6f1a63 685fbf35fbfaf67bf79e2f14db57f535fa8093c8 9cfc53ce649cface08f9db9dfa1a77f317bee6df 9d8521ff84fc4b683fcf27e39d69bfdaeff34777 e7bd715c8550856456eb97a7bfde20a892c7eaff 10328ffd2fe8ff12c446f513302fffa1ff33e3ff af883effadf21a33fa7fcabffb0078f47f0937fa df7325cfe8ffff4fec467d8d79f33ff47f7698ff 227e99ff88f90700000000000000000058ae0bcd cedba200280000
99 4