Кодировки и работа с файлами
(Если with не было в прошлых лекциях, то тут)
Оператор with
Есть такой design pattern — заранее задать finally при обработке исключения (например, закрыть открытый файл, даже если были исключения). В некоторых низкоуровневых языках нет исключений, но есть что-то типа «finalize»
протокол контекстного менеджера
.__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 print("Not working") 16 17 print("Done")
- →
Working <<</<class 'SyntaxError'>/шhат?/<traceback object at 0x7f5fe5bac500>>>> Done
Если .__exit__() возвращает пустой объект (в примере можно передать конструктору False), исключения, возникшие при выходе из контекста, распространяются на уровень выше (в примере — не ловятся)
Далее см. contextlib.html
Байтовые структуры
Тип 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», но это абсолютно прозрачно, кроме размера:
На момент 2025-11-15 документация по модулю encodings лежит в немного нестандартном месте: там же, в документации по codecs
Не всякие кодировки полны:"Вопрос".encode("koi8-r").decode("latin3")
Устарело ли понятие однобайтовой кодировки? Нет! бНОПНЯ живёт и процветает в Windows, например.
Имена Unicode
Uniode HOWTO в документации по Python
См. модуль unicodedata + спецсимволы \uxxxx и \N{имя}:
Просто файлы
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'
- Соответствие типов не проверяется (только имя). Если процедура десериализации не перегружена, подойдёт даже пустой класс:
небезопасно, т. к. потенциально можно исполнить произвольный код, напиханный злоумышленником
TODO в 3.14 поменяли протокол на 5, надо будет переделать пример
Структуры типа Си
Что мешает записать / считать представление объекта в памяти — это и будет в точности его контент?
- Правильный ответ: всё можно, «но есть нюансы» ☺
- Остроконечники и тупоконечники (порядок байтов в слове), 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)
TODO Changed in version 3.14: Added support for the 'F' and 'D' formats. — комплексные (для GPU?)
Базы данных и 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 < нетекстовый_файл
Если вдруг вы используете Windows и среду разработки с кодировкой, отличной от UTF-8 (например, CP-1251), настала пора что-то менять.
Собственно, задание:
Прощёлкать примеры с файлами в Tutorial, а также примеры по pickle и struct
EJudge: ZipInfo 'Размер архива'
Input:Написать программу, которой на стандартный ввод подаётся zip-архив в виде шестнадцатеричного дампа (последовательность шестнадцатеричных цифр, возможно, разделённых пробелами и переводами строки), а на выходе она показывает количество и суммарный объём хранящихся в нём файлов, если их распаковать.
Output:504b03040a0000000000d6a07c5100000000000000000000000002001c00 6f2f5554090003a483c25fab83c25f75780b000104f501000004f5010000 504b03040a00000000000ea77c5100000000000000000000000004001c00 6f2f312f55540900034c8fc25f568fc25f75780b000104f501000004f501 0000504b03040a0000000000d8a07c510000000000000000000000000600 1c006f2f312f352f5554090003a783c25fab83c25f75780b000104f50100 0004f5010000504b03040a00000000000da77c5100000000000000000000 000006001c006f2f312f322f5554090003498fc25f568fc25f75780b0001 04f501000004f5010000504b03040a00000000000da77c514b8325172100 0000210000000a001c006f2f312f322f646174655554090003498fc25f56 8fc25f75780b000104f501000004f5010000d0a1d0b120d0bdd0bed18f20 32382032303a35363a3235204d534b20323032300a504b03040a00000000 0066a67c5100000000000000000000000008001c006f2f312f322f332f55 54090003108ec25f3f8ec25f75780b000104f501000004f5010000504b03 040a00000000000aa77c51ba7488890b0000000b0000000b001c006f2f31 2f322f332f63616c5554090003438fc25f568fc25f75780b000104f50100 0004f5010000323032302d31312d32380a504b03040a0000000000d6a07c 510000000000000000000000000a001c006f2f312f322f332f342f555409 0003a483c25fab83c25f75780b000104f501000004f5010000504b03040a 00000000000ea77c5100000000000000000000000008001c006f2f312f6e 6f6e6555540900034c8fc25f568fc25f75780b000104f501000004f50100 00504b01021e030a0000000000d6a07c5100000000000000000000000002 0018000000000000001000ed41000000006f2f5554050003a483c25f7578 0b000104f501000004f5010000504b01021e030a00000000000ea77c5100 0000000000000000000000040018000000000000001000ed413c0000006f 2f312f55540500034c8fc25f75780b000104f501000004f5010000504b01 021e030a0000000000d8a07c510000000000000000000000000600180000 00000000001000ed417a0000006f2f312f352f5554050003a783c25f7578 0b000104f501000004f5010000504b01021e030a00000000000da77c5100 0000000000000000000000060018000000000000001000ed41ba0000006f 2f312f322f5554050003498fc25f75780b000104f501000004f501000050 4b01021e030a00000000000da77c514b83251721000000210000000a0018 000000000001000000a481fa0000006f2f312f322f646174655554050003 498fc25f75780b000104f501000004f5010000504b01021e030a00000000 0066a67c51000000000000000000000000080018000000000000001000ed 415f0100006f2f312f322f332f5554050003108ec25f75780b000104f501 000004f5010000504b01021e030a00000000000aa77c51ba7488890b0000 000b0000000b0018000000000001000000a481a10100006f2f312f322f33 2f63616c5554050003438fc25f75780b000104f501000004f5010000504b 01021e030a0000000000d6a07c510000000000000000000000000a001800 0000000000001000ed41f10100006f2f312f322f332f342f5554050003a4 83c25f75780b000104f501000004f5010000504b01021e030a0000000000 0ea77c51000000000000000000000000080018000000000000000000a481 350200006f2f312f6e6f6e6555540500034c8fc25f75780b000104f50100 0004f5010000504b05060000000009000900b7020000770200000000
3 44
EJudge: BoxDrawing 'Рисуем рамочки'
Input:Сначала вводится строка, которая состоит из «слов», разделённых пробельными символами. В следующей строке вводится через пробел: ширина «экрана» (в знакоместах), толщина вертикальных и толщина горизонтальных линий. Толщина — это слово LIGHT или HEAVY. Требуется вывести все слова, перенося их на следующую строку и обводя рамкой. Правый край рамки должен быть выровнен по ширине экрана, лишние пробелы — добавлены к последнему слову. Слово всегда уже экрана не менее, чем на 2. Рамка набирается из UNICODE-символов блока «Box Drawing» с соответствующими именами.
Output:Гнев, богиня, воспой Ахиллеса, Пелеева сына, грозный, который ахеянам тысячи бедствий соделал 42 HEAVY LIGHT
— Задача на «перевод»: какое-то описание → имя Unicode → символ┎─────┰───────┰──────┰─────────┰─────────┒ ┃Гнев,┃богиня,┃воспой┃Ахиллеса,┃Пелеева ┃ ┠─────╂───────┸┰─────┸─┰───────╂─────────┨ ┃сына,┃грозный,┃который┃ахеянам┃тысячи ┃ ┠─────┸──┰─────┸───────┸───────┸─────────┨ ┃бедствий┃соделал ┃ ┖────────┸───────────────────────────────┚
— Просмотр корневого каталога в образе 1440 флоппи-диска
EJudge: FragEncode 'Кодировки цитат'
Input:Вводится поток данных следующей структуры: в начале идёт некоторый текст в кодировке UTF-8. Затем — нулевой байт. Затем несколько фрагментов в случайной кодировке из набора CP866, CP1251, KOI8-R или ISO-8859-5, после каждого их которых снова следует нулевой байт. Для каждого фрагмента определить, является ли он (после соответствующей перекодировки) точной цитатой из текста, в этом случае вывести "Yes", идаче — "No". В примере показан шестнадцатеричный дамп, сам файл см. в приложении.
Output:00000000 d0 be 20 d1 82 d0 be d0 bc 2c 20 d1 87 d1 82 d0 |.. ......, .....| 00000010 be d0 b1 d1 8b 20 d0 b4 d1 83 d1 88 d0 b5 d0 b2 |..... ..........| 00000020 d0 bd d0 be d0 b5 20 d0 bd d0 b0 d1 81 d1 82 d1 |...... .........| 00000030 80 d0 be d0 b5 d0 bd d0 b8 d0 b5 20 d0 bc d0 be |........... ....| 00000040 d0 b3 d0 bb d0 be 20 d1 82 d0 be d1 82 d1 87 d0 |...... .........| 00000050 b0 d1 81 20 d0 b6 d0 b5 20 d0 b8 d0 b7 d0 bc d0 |... .... .......| 00000060 b5 d0 bd d0 b8 d1 82 d1 8c 20 d0 b5 d0 b3 d0 be |......... ......| 00000070 20 d0 b2 0a d1 81 d0 be d0 bf d1 80 d0 b8 d0 ba | ...............| 00000080 d0 be d1 81 d0 bd d0 be d0 b2 d0 b5 d0 bd d0 b8 |................| 00000090 d0 b8 20 d1 81 20 d0 b4 d0 b5 d0 b9 d1 81 d1 82 |.. .. ..........| 000000a0 d0 b2 d0 b8 d1 82 d0 b5 d0 bb d1 8c d0 bd d0 be |................| 000000b0 d1 81 d1 82 d1 8c d1 8e 2e 20 d0 9d d0 b5 20 d0 |......... .... .| 000000c0 b4 d0 be d0 b5 d0 b7 d0 b6 d0 b0 d1 8f 00 de 20 |............... | 000000d0 e2 de dc 2c 20 dd d0 e1 e2 e0 de d5 dd d8 d5 20 |..., .......... | 000000e0 d4 e3 e8 d5 d2 dd de d5 20 e7 e2 de d1 eb 20 dc |........ ..... .| 000000f0 de d3 db de 00 c9 da cd c5 ce c9 d4 d8 20 c5 c7 |............. ..| 00000100 cf 20 d7 0a d3 cf d0 d2 c9 cb cf d3 ce cf d7 c5 |. ..............| 00000110 ce c9 c9 20 d3 20 c4 c5 ca d3 d4 d7 c9 d4 c5 cc |... . ..........| 00000120 d8 ce cf d3 d4 d8 c0 2e 20 ee c5 00 |........ ...|
No Yes
