Главная страница
Навигация по странице:

  • Формат Средство Чтение+ запись строк кода Всего строк кода Размер выход ного файла ( Кбайт)

  • Запись и чтение двоичных данных

  • Консервирование с возможным сжатием

  • __dict__() ,стр. 422 Запись и чтение двоичных данных343

  • Типы данных bytes и bytearray

  • Синтаксис Описание 348 Глава 7. Работа с файламиНеформатированные двоичные данные с возможным сжатием

  • Программирование на Python 3. Руководство издательство СимволПлюс


    Скачать 3.74 Mb.
    НазваниеРуководство издательство СимволПлюс
    Дата10.11.2022
    Размер3.74 Mb.
    Формат файлаpdf
    Имя файлаПрограммирование на Python 3.pdf
    ТипРуководство
    #780382
    страница40 из 74
    1   ...   36   37   38   39   40   41   42   43   ...   74

    332
    Глава 6. Объектно/ориентированное программирование
    Упражнения
    Первые два упражнения связаны с модификацией классов, о которых рассказывалось в этой главе. Последние два упражнения связаны с созданием новых классов с самого начала.
    1. Измените класс Point (из модуля Shape.py или ShapeAlt.py) так, что
    бы обеспечить поддержку следующих операций, где p, q и r являют
    ся объектами типа Point, а n – число.
    p = q + r # Point.__add__()
    p += q # Point.__iadd__()
    p = q  r # Point.__sub__()
    p = q # Point.__isub__()
    p = q * n # Point.__mul__()
    p *= n # Point.__imul__()
    p = q / n # Point.__truediv__()
    p /= n # Point.__itruediv__()
    p = q // n # Point.__floordiv__()
    p //= n # Point.__ifloordiv__()
    Каждый из методов реализации комбинированных инструкций присваивания будет состоять всего из четырех строк программного кода, а все остальные методы – из двух, включая строку с инструк
    цией def, и, конечно же, все они очень просты и похожи между со
    бой. С минимальным описанием и доктестом для каждого из них всего добавится порядка ста тридцати новых строк. Пример реше
    ния приводится в файле Shape_ans.py, аналогичное решение также приводится в файле ShapeAlt_ans.py.
    2. Измените класс в файле Image.py так, чтобы в нем появился метод resize(width, height)
    . Если новая ширина или высота меньше теку
    щего значения, все цвета, оказавшиеся за пределами новых границ изображения, должны удаляться. Если в качестве нового значения ширины или высоты передается None, соответствующее значение ширины или высоты должно оставаться без изменений. Наконец,
    не забудьте воссоздавать множество self.__colors. Возвращаемое логическое значение должно свидетельствовать о том, были ли про
    изведены изменения размеров или нет. Всю реализацию метода можно уместить в 20 строк (в 35, включая строку документирова
    ния и простейший доктест). Пример решения приводится в файле
    Image_ans.py
    3. Создайте класс Transaction, который хранит сумму, дату, валюту
    (по умолчанию «USD» – доллар США), курс валюты по отношению к доллару (по умолчанию 1) и описание (по умолчанию None). Все ат
    рибуты данных должны быть частными. Реализуйте следующие свойства, доступные только для чтения: amount, date, currency,
    usd_conversion_rate
    , description и usd (вычисляется, как amount *
    usd_conversion_rate
    ). Реализацию класса можно уместить в шестьде
    сят строк программного кода, включая несколько простейших док

    Упражнения
    333
    тестов. Пример решения (этого упражнения и следующего) приво
    дится в файле Account.py.
    4. Реализуйте класс Account, который хранил бы номер счета, назва
    ние счета и список транзакций (объектов класса Transaction). Номер счета должен быть реализован в виде свойства, доступного только для чтения. Название счета должно быть реализовано в виде свой
    ства, доступного для чтения и для записи с проверкой длины назва
    ния, которое должно содержать не менее четырех символов. Класс должен поддерживать встроенную функцию len() (возвращая чис
    ло транзакций) и содержать два вычисляемых свойства, доступных только для чтения: balance, возвращающее баланс счета в долларах
    США, и all_usd, возвращающее True, если все транзакции выполня
    лись в долларах США, или False – в противном случае. Добавьте три дополнительных метода: apply() для добавления транзакции,
    save()
    и load(). Методы save() и load() должны сохранять и загру
    жать объекты в двоичном формате, в файле, имя которого совпада
    ет с номером счета и с расширением .acc. Они должны сохранять и загружать номер счета, название счета и все транзакции. Реали
    зацию класса можно уместить в девяносто строк программного ко
    да вместе с несколькими простейшими доктестами, включающими проверку операций сохранения и загрузки с помощью такого про
    граммного кода, как name = os.path.join(tempfile.gettempdir(), ac
    count_name)
    , который позволяет получить подходящее имя времен
    ного файла. Требуется удалить временные файлы по завершении доктестов. Пример решения приводится в файле Account.py.

    7
    Работа с файлами
    В большинстве программ возникает необходимость сохранять инфор
    мацию (например, данные или информацию о состоянии) в файлах и загружать ее из файлов. В языке Python имеется множество различ
    ных способов выполнять эти действия. В главе 3 мы уже коротко рас
    сматривали вопросы работы с текстовыми файлами, а в предыдущей главе обсуждали вопрос «консервирования» объектов. В этой главе мы более детально рассмотрим работу с файлами.
    Все приемы, представленные в этой главе, не зависят от типа исполь
    зуемой платформы. Это означает, что файл, сохраненный любым из примеров программ в одной операционной системе и аппаратной архи
    тектуре, может быть загружен в другой операционной системе и на другой аппаратной архитектуре. Это утверждение может быть спра
    ведливым и для ваших программ тоже, если вы будете использовать те же приемы, что и в представленных здесь примерах программ.
    В первых трех разделах главы рассматриваются общие случаи сохра
    нения на диске и загрузки с диска целых коллекций данных. В первом разделе будет показано, как это можно реализовать с использованием двоичных форматов файлов, причем в первом подразделе описывается применение модуля pickle (с возможным сжатием), а во втором разде
    ле демонстрируется, как ту же работу можно выполнить вручную. Во втором разделе рассматриваются приемы работы с текстовыми файла
    ми. Запись информации в файлы выполняется очень просто, но обрат
    ное чтение может оказаться непростым делом, особенно, когда прихо
    дится иметь дело с не текстовыми данными, такими как числа и даты.
    Мы рассмотрим два подхода к синтаксическому разбору текста: вруч
    ную и с использованием регулярных выражений. Третий раздел рас
    сказывает, как читать и писать файлы в формате XML. В этом разделе

    Запись и чтение двоичных данных

    Запись и синтаксический анализ текстовых файлов

    Запись и синтаксический анализ файлов XML

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

    Запись и чтение двоичных данных
    335
    будет показано, как писать и читать такие файлы с применением де
    ревьев элементов, объектной модели документа (Document Object Mo
    del, DOM), а также как выполнять запись вручную и анализировать файлы с использованием парсера SAX (Simple API for XML – упро
    щенный API для работы с XML).
    Четвертый раздел демонстрирует, как можно организовать произволь
    ный доступ к данным в двоичных файлах. Это удобно, когда все эле
    менты данных имеют одинаковый размер и когда количество элемен
    тов в файле больше, чем нам требуется (или возможно) хранить в па
    мяти.
    Какой формат файлов является более предпочтительным для хране
    ния целых коллекций – двоичный, текстовый или XML? Как лучше работать с каждым из форматов? Ответы на эти вопросы слишком сильно зависят от конкретной ситуации, чтобы на них можно было дать единственный категоричный ответ, тем более что каждый формат имеет свои достоинства и недостатки, так же как и каждый из спосо
    бов работы с ними. Мы рассмотрим каждый из них, чтобы вы могли принимать обоснованные решения в зависимости от ситуации.
    При использовании двоичных форматов обычно достигается очень вы
    сокая скорость сохранения и загрузки, а, кроме того, они могут быть очень компактными. Двоичные данные не требуется анализировать,
    потому что каждый тип данных сохраняется в своем естественном представлении. Двоичные данные не могут читаться или редактиро
    ваться человеком, а без точного знания формата невозможно создать отдельные инструменты для работы с двоичными данными.
    Текстовые форматы легко могут читаться и редактироваться челове
    ком, что упрощает их обработку отдельными инструментами или из
    менение с помощью текстового редактора. Парсинг текстовых форма
    тов может оказаться далеко не простым делом, и не всегда просто бы
    вает выдать сообщение об ошибке, если формат текстового файла нару
    шен (например, в результате небрежного редактирования).
    Файлы в формате XML могут читаться и редактироваться человеком,
    хотя они содержат большой объем служебной информации, что приво
    дит к увеличению объемов файлов. Подобно текстовому формату, фор
    мат XML может обрабатываться отдельными инструментами. Парсинг файлов XML выполняется достаточно просто (при условии, что пар
    синг выполняется с помощью парсера XML, а не вручную), и некото
    рые парсеры способны выдавать весьма информативные сообщения об ошибках. Однако парсеры XML могут быть очень медленными, поэто
    му чтение очень больших файлов XML может занимать значительно больше времени, чем чтение эквивалентных им двоичных или тексто
    вых файлов. Формат XML содержит такие метаданные, как информа
    цию о кодировке символов (явно или неявно), которые нечасто встре
    тишь в текстовых файлах, и это обеспечивает более высокую переноси
    мость для файлов XML, чем для текстовых файлов.

    336
    Глава 7. Работа с файлами
    Текстовые форматы обычно более удобны для конечного пользовате
    ля, но иногда значительные проблемы производительности делают двоичный формат единственным разумным выбором. Тем не менее всегда полезно предусмотреть возможность импорта/экспорта для формата XML, что обеспечит возможность обработки файлов инстру
    ментами сторонних разработчиков и не помешает использованию тек
    стового или двоичного формата в процессе нормальной работы самой программы.
    В трех первых разделах этой главы будет использоваться одна и та же коллекция данных: множество записей об авиационных инцидентах.
    В табл. 7.1 показаны имена и типы данных полей, а также ограниче
    ния, накладываемые на записи об авиационных инцидентах. В дейст
    вительности совершенно неважно, какие данные мы обрабатываем.
    Для нас сейчас важно научиться обрабатывать фундаментальные типы данных, включая строки, целые числа, числа с плавающей точкой, ло
    гические значения и даты – научившись обрабатывать эти типы дан
    ных, мы без труда сможем обрабатывать любые другие типы данных.
    Таблица 7.1. Содержимое одной записи в файле данных
    авиационных инцидентов
    Используя один и тот же набор данных об авиационных инцидентах и сохраняя его в двоичном, текстовом и XML форматах, мы получаем возможность сравнить различные форматы и объем программного ко
    да, необходимого для работы с ними. В табл. 7.2 приводится число строк программного кода, необходимого для реализации операций чтения и записи в каждом из форматов.
    Имя
    Тип данных
    Примечания
    report_id str
    Минимальная длина 8 символов,
    без пробельных символов date datetime.date airport str
    Непустое, без символов перевода строки aircraft_id str
    Непустое, без символов перевода строки aircraft_type str
    Непустое, без символов перевода строки pilot_percent_hours_on_type float
    В диапазоне от 0.0 до 100.0
    pilot_total_hours int
    Положительное и ненулевое зна
    чения midair bool narrative str
    Многострочный текст

    Запись и чтение двоичных данных
    337
    Таблица 7.2. Сравнение средств чтения/записи для формата файлов
    с данными об авиационных инцидентах
    В таблице приводятся приблизительные размеры файлов, содержа
    щих записи о 596 авиационных инцидентах.
    1
    Размеры сжатых файлов с теми же данными, сохраненными под различными именами, могут отличаться на несколько байтов, так как имена файлов, которые могут иметь разную длину, включаются в состав сжатых данных. Точно так же могут отличаться размеры файлов XML, потому что одни средства записи в формате XML замещают кавычки в тексте сущностями
    (" для " и ' для '), а другие – нет.
    Во всех трех первых разделах рассматривается программный код од
    ной и той же программы: convertincidents.py. Эта программа исполь
    зуется для чтения информации об инцидентах в одном формате и для записи в другом формате. Ниже приводится справочный текст, кото
    рый выводится программой в консоли. (Мы немного отформатировали текст, чтобы уместить его в ширину книжной страницы.)
    Usage: convertincidents.py [options] infile outfile
    Reads aircraft incident data from infile and writes the data to outfile. The data formats used depend on the file extensions:
    .aix is XML, .ait is text (UTF8 encoding), .aib is binary,
    .aip is pickle, and .html is HTML (only allowed for the outfile).
    All formats are platformindependent.
    Формат
    Средство
    Чтение+
    запись
    строк кода
    Всего
    строк
    кода
    Размер выход
    ного файла
    (

    Кбайт)
    Двоичный Модуль pickle (со сжатием gzip) 20 + 16 =
    36 160
    Двоичный Модуль pickle
    20 + 16 =
    36 416
    Двоичный Вручную (со сжатием gzip)
    60 + 34 =
    94 132
    Двоичный Вручную
    60 + 34 =
    94 356
    Текст
    Чтение с использованием регу
    лярных выражений, запись вручную
    39 + 28 =
    67 436
    Текст
    Вручную
    53 + 28 =
    81 436
    XML
    Дерево элементов
    37 + 27 =
    64 460
    XML
    DOM
    44 + 36 =
    80 460
    XML
    Чтение с использованием пар
    сера SAX, запись вручную
    55 + 37 =
    92 464 1
    В примерах используются реальные данные об авиационных инцидентах,
    которые можно найти на сайте FAA (Федеральное авиационное управление правительства США, www.faa.gov)

    338
    Глава 7. Работа с файлами
    Options:
    h, help show this help message and exit
    f, force write the outfile even if it exists [default: off]
    v, verbose report results [default: off]
    r READER, reader=READER
    reader (XML): 'dom', 'd', 'etree', 'e', 'sax', 's'
    reader (text): 'manual', 'm', 'regex', 'r'
    [default: etree for XML, manual for text]
    w WRITER, writer=WRITER
    writer (XML): 'dom', 'd', 'etree', 'e',
    'manual', 'm' [default: manual]
    z, compress compress .aib/.aip outfile [default: off]
    t, test execute doctests and exit (use with v for verbose)
    (Перевод:
    Порядок использования: convertincidents.py [параметры] infile outfile
    Читает данные об авиационных инцидентах из файла infile и записывает их в файл outfile. Форматы файлов определяются по их расширениям:
    .aix – XML, .ait – текст (в кодировке UTF8), .aib – двоичный,
    .aip – формат модуля pickle и .html – HTML (только для выходного файла outfile).
    Все форматы являются платформонезависимыми.
    Параметры:
    h, help вывести текст этого сообщения и выйти
    f, force выполнять запись в outfile, даже если он существует
    [по умолчанию запись в существующий файл не производится]
    v, verbose вывести результаты [по умолчанию: отключено]
    r READER, reader=READER
    средство чтения (XML): 'dom', 'd', 'etree', 'e', 'sax', 's'
    средство чтения (текст): 'manual', 'm', 'regex', 'r'
    [по умолчанию: etree для XML, manual для текста]
    w WRITER, writer=WRITER
    средство записи (XML): 'dom', 'd', 'etree', 'e',
    'manual', 'm' [по умолчанию: manual]
    z, compress сжатие для выходных файлов .aib/.aip
    [по умолчанию: отключено]
    t, test выполнить доктесты и выйти (для вывода подробного отчета о прохождении тестов используйте параметр v) конец перевода)
    Параметры, используемые программой, более сложные, чем обычно могло бы потребоваться конечному пользователю, которого мало бес
    покоит, какое средство чтения или записи используется для любого из поддерживаемых форматов. В более реалистичной версии программы параметры, управляющие выбором средств чтения и записи, отсутст
    вовали бы, и мы просто использовали бы по одному средству чтения и одному средству записи для каждого из форматов. Точно так же в окончательной версии программы отсутствовал бы параметр тести
    рования, который предоставляется исключительно для того, чтобы мы могли протестировать программный код.

    Запись и чтение двоичных данных
    339
    Программа определяет собственное исключение:
    class IncidentError(Exception): pass
    Информация об авиационных инцидентах хранится в виде объектов класса Incident. Ниже приводится строка с инструкцией class и метод инициализации:
    class Incident:
    def __init__(self, report_id, date, airport, aircraft_id,
    aircraft_type, pilot_percent_hours_on_type,
    pilot_total_hours, midair, narrative=""):
    assert len(report_id) >= 8 and len(report_id.split()) == 1, \
    "invalid report ID"
    self.__report_id = report_id self.date = date self.airport = airport self.aircraft_id = aircraft_id self.aircraft_type = aircraft_type self.pilot_percent_hours_on_type = pilot_percent_hours_on_type self.pilot_total_hours = pilot_total_hours self.midair = midair self.narrative = narrative
    При создании объекта Incident проверяется идентификатор отчета и де
    лается доступным только для чтения в виде свойства repotr_id. Все ос
    тальные атрибуты данных представляют собой свойства, доступные для чтения и для записи. Например, ниже приводится программный код объявления свойства date:
    @property def date(self):
    return self.__date
    @date.setter def date(self, date):
    assert isinstance(date, datetime.date), "invalid date"
    self.__date = date
    Все остальные свойства объявляются точно так же, отличаясь только некоторыми особенностями инструкции assert, поэтому мы не будем приводить их здесь. Поскольку для проверки мы используем инструк
    ции assert, программа будет завершаться аварийно при любой попыт
    ке создать объект Incident с недопустимыми данными или при попыт
    ке записать недопустимое значение в любое из свойств, доступных для чтения/записи. Такой бескомпромиссный подход был выбран потому,
    что нам необходимо гарантировать допустимость загружаемых и со
    храняемых данных, а в случае ошибки нам требуется, чтобы програм
    ма сообщала о ней и завершала свою работу, вместо того чтобы просто продолжать работу.
    Коллекция данных об инцидентах хранится в объекте типа Incident
    Collection
    . Этот класс наследует класс dict, благодаря чему мы получа

    340
    Глава 7. Работа с файлами ем в свое распоряжение массу функциональных возможностей, таких как поддержка оператора доступа к элементам ([]) для получения, соз
    дания и удаления отдельных записей об инцидентах. Ниже приводит
    ся строка с инструкцией class и несколько методов класса:
    class IncidentCollection(dict):
    def values(self):
    for report_id in self.keys():
    yield self[report_id]
    def items(self):
    for report_id in self.keys():
    yield (report_id, self[report_id])
    def __iter__(self):
    for report_id in sorted(super().keys()):
    yield report_id keys = __iter__
    Нам не потребовалось переопределять специальный метод инициализа
    ции, потому что вполне достаточно функциональности унаследованно
    го метода dict.__init__(). Ключами словаря являются идентификаторы отчетов, а значениями – объекты Incident. Мы переопределили методы values()
    , items() и keys() так, чтобы возвращаемые ими итераторы обес
    печивали выполнение итераций в порядке сортировки идентифика
    торов отчетов. Такое поведение обусловлено тем, что методы values()
    и items() используют итератор по ключам, возвращаемый методом
    IncidentCollection.keys()
    , а этот метод (который имеет еще одно имя:
    IncidentCollection.__iter__()
    ) выполняет в порядке сортировки итера
    ции по ключам, возвращаемым методом dict.keys() базового класса.
    Дополнительно класс IncidentCollection имеет методы export() и im
    port_()
    . (Мы использовали завершающий символ подчеркивания, что
    бы обеспечить отличие имени метода от встроенной инструкции im
    port
    .) Методу export() передается имя файла и в виде необязательных аргументов – средство записи и флаг сжатия, а он на основе имени файла и средства записи передает управление более конкретному мето
    ду, такому как export_xml_dom() или export_xml_etree(). Метод import_()
    принимает имя файла и средство чтения в виде необязательного аргу
    мента и работает похожим образом. Методам импортирования, рабо
    тающим с двоичными форматами, не передается информация о том,
    был ли сжат файл – как ожидается, они сами будут определять это и работать соответственно.
    Запись и чтение двоичных данных
    Двоичные форматы даже без сжатия обычно являются более компакт
    ными и, как правило, обеспечивают более высокую скорость сохране

    Запись и чтение двоичных данных
    341
    ния и загрузки. Наиболее простой способ заключается в использова
    нии модуля pickle, хотя при обработке двоичных данных вручную обычно получаются файлы меньшего размера.
    Консервирование с возможным сжатием
    Консервирование является наиболее простым подходом к вы
    полнению операций сохранения и загрузки данных в програм
    мах на языке Python, но, как уже отмечалось в предыдущей главе, процедура консервирования не имеет механизмов обеспе
    чения безопасности (шифрование, цифровая подпись), поэтому загрузка законсервированных объектов из непроверенных ис
    точников может оказаться опасной. Проблема безопасности обусловлена тем, что законсервированные объекты могут им
    портировать произвольные модули и вызывать произвольные функции, то есть можно создать такой законсервированный объект, который, к примеру, после загрузки будет заставлять интерпретатор выполнять неблаговидные действия. Тем не ме
    нее консервирование часто является идеальным средством для работы с узкоспециализированными данными, особенно в про
    граммах, предназначенных для личного пользования.
    Обычно намного проще сначала выработать формат файла и написать программный код, выполняющий сохранение, а потом написать про
    граммный код, выполняющий загрузку, поэтому мы начнем с того,
    что рассмотрим реализацию консервирования коллекции записей об инцидентах.
    def export_pickle(self, filename, compress=False):
    fh = None try:
    if compress:
    fh = gzip.open(filename, "wb")
    else:
    fh = open(filename, "wb")
    pickle.dump(self, fh, pickle.HIGHEST_PROTOCOL)
    return True except (EnvironmentError, pickle.PicklingError) as err:
    print("{0}: export error: {1}".format(
    os.path.basename(sys.argv[0]), err))
    return False finally:
    if fh is not None:
    fh.close()
    Если было запрошено сжатие, для открытия файла используется функция gzip.open() из модуля gzip, в противном случае используется встроенная функция open(). При консервировании данных в двоичном формате мы должны использовать двоичный режим записи ("wb").

    342
    Глава 7. Работа с файлами
    (В Python 3.0 константа pickle.HIGHEST_PROTOCOL обозначает протокол 3,
    соответствующий компактному двоичному формату.
    1
    )
    В случае появления ошибок мы предпочитаем сразу же сообщать о них пользователю и возвращать вызываю
    щей программе логическое значение, свидетельствую
    щее об успехе или неудаче. В методе используется блок finally
    , чтобы обеспечить закрытие файла независимо от наличия ошибки. В главе 8 будет представлен более ком
    пактный способ закрытия файлов, в котором не исполь
    зуется блок finally.
    Этот программный код очень напоминает то, что мы уже видели в пре
    дыдущей главе, но здесь есть один тонкий момент, о котором необхо
    димо упомянуть. Консервированию подвергается объект self класса dict
    . Но значениями словаря являются объекты класса Incident, то есть объекты нашего собственного класса. Модуль pickle достаточно интеллектуален, чтобы сохранять объекты почти любых наших клас
    сов без нашего вмешательства.
    Вообще, консервироваться могут логические значения,
    числа и строки, а также экземпляры классов, включая нестандартные классы, предоставляющие частный атри
    бут __dict__. Кроме того, консервироваться могут любые встроенные типы коллекций (кортежи, списки, множе
    ства, словари), если они содержат только объекты, до
    пускающие возможность консервирования (включая коллекции, то есть поддерживаются рекурсивные струк
    туры). Имеется также возможность консервировать дру
    гие типы объектов или экземпляры нестандартных классов, которые обычно не могут консервироваться (на
    пример, потому что они имеют атрибуты, не допускаю
    щие возможность консервирования), для чего достаточ
    но или оказать некоторую помощь модулю pickle, или реализовать функции сохранения и загрузки. Все необ
    ходимые подробности вы найдете в электронной доку
    ментации к модулю pickle.
    Чтобы прочитать законсервированные данные, необходимо опреде
    лить – были ли они сжаты или нет. Любой файл, сжатый с использова
    нием алгоритма gzip, начинается с сигнатуры файла (magic number).
    Сигнатура – это последовательность из одного или более байтов в нача
    ле файла, используемая для обозначения типа файла. Для обозначе
    ния файлов, сжатых с использованием алгоритма gzip, используется
    1
    Протокол 3 впервые появился только в Python 3. Если необходимо созда
    вать файлы, доступные для чтения и записи программам, работающим под управлением Python 2 и Python 3, необходимо использовать протокол 2.
    Менеджеры контекста, стр. 428
    Специальный метод
    __dict__()
    ,
    стр. 422

    Запись и чтение двоичных данных
    343
    сигнатура из двух байтов 0x1F 0x8B, которые мы сохраняем в перемен
    ной типа bytes:
    GZIP_MAGIC = b"\x1F\x8B"
    Подробнее о типе данных bytes рассказывается во врезке «Типы дан
    ных bytes и bytearray» (стр. 344), а в табл. 7.3 (стр. 345–347) перечис
    ляются их методы.
    Ниже приводится программный код, выполняющий чтение законсер
    вированных данных:
    def import_pickle(self, filename):
    fh = None try:
    fh = open(filename, "rb")
    magic = fh.read(len(GZIP_MAGIC))
    if magic == GZIP_MAGIC:
    fh.close()
    fh = gzip.open(filename, "rb")
    else:
    fh.seek(0)
    self.clear()
    self.update(pickle.load(fh))
    return True except (EnvironmentError, pickle.UnpicklingError) as err:
    print("{0}: import error: {1}".format(
    os.path.basename(sys.argv[0]), err))
    return False finally:
    if fh is not None:
    fh.close()
    Мы не знаем заранее, был файл сжат или нет. В любом случае, мы на
    чинаем с того, что открываем файл для чтения в двоичном режиме,
    а затем читаем первые два байта. Если эти два байта представляют сиг
    натуру gzip, файл закрывается и создается новый объект файла вызо
    вом функции gzip.open(). Если файл не был сжат, используется объект файла, созданный функцией open(); вызовом его метода seek() указа
    тель позиции в файле перемещается в начало, чтобы следующая опе
    рация чтения (выполняемая внутри функции pickle.load()) начала чтение файла с самого начала.
    Мы не можем выполнить прямое присваивание объекту self, так как это приведет к уничтожению используемого объекта типа IncidentCol
    lection
    , поэтому сначала мы удаляем все элементы словаря и затем с помощью метода dict.update() заполняем словарь объектами с ин
    формацией об инцидентах из словаря типа IncidentCollection, загру
    женного из файла.
    Обратите внимание, что порядок следования байтов в машинном слове для данной аппаратной архитектуры не имеет никакого значения, по

    344
    Глава 7. Работа с файлами тому что при чтении сигнатуры мы читаем два отдельных байта, а ко
    гда модуль pickle читает основные данные, он сам заботится о порядке следования байтов.
    Типы данных bytes и bytearray
    В языке Python имеется два типа данных, которые используются для работы с обычными байтами: тип bytes – неизменяемый и тип bytearray
    – изменяемый. Оба типа хранят последовательности из нуля или более 8битовых беззнаковых целых чисел (байтов),
    где каждый байт может представлять число в диапазоне 0…255.
    Оба типа очень похожи на строки и предоставляют практически те же методы, включая поддержку срезов. Кроме того, тип данных bytearray предостав
    ляет несколько методов, напоминающих методы класса list, позволяющих производить изменения внутри объекта bytearray. Все методы этих двух ти
    пов данных перечислены в табл. 7.3 (стр. 345–347).
    Несмотря на то, что операция извлечения среза для объектов bytes и bytearray возвращает объект того же самого типа, тем не менее оператор доступа к элементу ([]) возвращает объект типа int
    – значение заданного байта. Например:
    word = b"Animal"
    x = b"A"
    word[0] == x # вернет: False # word[0] == 65; x == b"A"
    word[:1] == x # вернет: True # word[:1] == b"A"; x == b"A"
    word[0] == x[0] # вернет: True # word[0] == 65; x[0] == 65
    Ниже приводятся еще несколько примеров использования объ
    ектов типа bytes и bytearray:
    data = b"5 Hills \x35\x20\x48\x69\x6C\x6C\x73"
    data.upper() # вернет: b'5 HILLS 5 HILLS'
    data.replace(b"ill", b"at") # вернет: b'5 Hats 5 Hats'
    bytes.fromhex("35 20 48 69 6C 6C 73") # вернет: b'5 Hills'
    bytes.fromhex("352048696C6C73") # вернет: b'5 Hills'
    data = bytearray(data) # теперь data имеет тип bytearray data.pop(10) # вернет: 72 (ord("H"))
    data.insert(10, ord("B")) # data == b'5 Hills 5 Bills'
    Методы, имеющие смысл только применительно к строкам, та
    кие как bytes.upper(), предполагают, что байты соответствуют символам из набора ASCII. Метод класса bytes.fromhex() игнори
    рует пробелы и интерпретирует каждую подстроку из двух цифр как шестнадцатеричное число, то есть строка "35" будет преобра
    зована в байт со значением 0x35, и т. д.
    Метод
    str.
    transla
    te()
    , стр. 99

    Запись и чтение двоичных данных
    345
    Таблица 7.3. Методы объектов типа bytes и bytearray
    Синтаксис
    Описание
    ba.append(i)
    Добавляет целое число i (в диапазоне 0…255) в объект ba ти
    па bytearray b.capitalize()
    Возвращает копию объекта b типа bytes или bytearray, с пер
    вым символом в верхнем регистре (если это символ ASCII)
    b.center(width,
    byte)
    Возвращает копию объекта b, отцентрированную в поле ши
    риной width. Недостающие символы по умолчанию заполня
    ются пробелами или символами, в соответствии с необяза
    тельным аргументом byte
    b.count
    (x, start, end)
    Возвращает число вхождений объекта x типа bytes или byte
    array
    , в объект b типа bytes или bytearray (или в срез b[start:end]
    )
    b.decode
    (encoding,
    error)
    Возвращает объект типа str, представляю
    щий результат декодирования байтов с ис
    пользованием кодировки UTF8 или коди
    ровки, определяемой аргументом encoding,
    с обработкой ошибок, определяемой необя
    зательным аргументом err b.endswith
    (x, start, end)
    Возвращает True, если b (или срез b[start:end]) оканчивает
    ся содержимым объекта x типа bytes или bytearray или лю
    бым из объектов типа bytes или bytearray в кортеже x;
    в противном случае возвращает False b.expandtabs
    (size)
    Возвращает копию объекта b, в котором символы табуля
    ции замещены пробелами с шагом 8 или в соответствии со значением необязательного аргумента size
    ba.extend(seq)
    Дополняет объект ba типа bytearray целыми числами из по
    следовательности seq. Все целые числа должны находиться в диапазоне 0…255
    b.find
    (x, start, end)
    Возвращает позицию самого первого (крайнего слева) вхож
    дения объекта x типа bytes/bytearray в объект b (или в срез b[start:end]
    ); если объект x не найден, возвращается –1. Для поиска самого последнего (крайнего справа) вхождения сле
    дует использовать метод rfind()
    b.fromhex(h)
    Возвращает объект типа bytes, который содержит байты, со
    ответствующие шестнадцатеричным значениям в строке h b.index
    (x, start, end)
    Возвращает позицию самого первого (крайнего слева) вхож
    дения объекта x в объект b (или в срез строки b[start:end]);
    если объект x не найден, возбуждается исключение ValueEr
    ror
    . Для поиска самого последнего (крайнего справа) вхож
    дения следует использовать метод rindex()
    ba.insert(p, i)
    Вставляет целое число i (в диапазоне 0…255) в позицию p в объекте ba
    Кодировки символов, стр. 112

    346
    Глава 7. Работа с файлами
    Таблица 7.3 (продолжение)
    Синтаксис
    Описание
    b.isalnum()
    Возвращает True, если объект b типа bytes/bytearray не пус
    той и содержит только алфавитноцифровые символы ASCII
    b.isalpha()
    Возвращает True, если объект b типа bytes/bytearray не пус
    той и содержит только алфавитные символы ASCII
    b.isdigit()
    Возвращает True, если объект b типа bytes/bytearray не пус
    той и содержит только цифровые символы ASCII
    b.islower()
    Возвращает True, если объект b типа bytes/bytearray содер
    жит хотя бы один символ ASCII, который может быть пред
    ставлен в нижнем регистре, и все такие символы находятся в нижнем регистре b.isspace()
    Возвращает True, если объект b типа bytes/bytearray не пус
    той и содержит только пробельные символы из набора ASCII
    b.istitle()
    Возвращает True, если объект b не пустой и имеет формат за
    головка b.isupper()
    Возвращает True, если объект b типа bytes/bytearray содер
    жит хотя бы один символ ASCII, который может быть пред
    ставлен в верхнем регистре, и все такие символы находятся в верхнем регистре b.join(seq)
    Объединяет все элементы типа bytes/bytearray в последова
    тельности seq, вставляя между ними объект b (который мо
    жет быть пустым)
    b.ljust
    (width,
    byte)
    Возвращает копию объекта b типа bytes/bytearray выровнен
    ной по левому краю в поле шириной width. Недостающие символы по умолчанию заполняются пробелами или симво
    лами, в соответствии с необязательным аргументом byte.
    Для выравнивания по правому краю используйте метод rjust()
    b.lower()
    Возвращает копию объекта b типа bytes/bytearray, в кото
    ром все символы ASCII приведены к нижнему регистру b.partition(sep)
    Возвращает кортеж с тремя объектами типа bytes: часть b перед самым первым вхождением содержимого объекта sep,
    сам объект sep и часть b после самого первого вхождения со
    держимого объекта sep. Если содержимое объекта sep не бу
    дет найдено, возвращается объект b и два пустых объекта bytes
    . Для деления объекта b по самому правому вхождению содержимого объекта sep используйте метод rpartition()
    ba.pop(p)
    Удаляет и возвращает целое число, находящееся в объекте ba в позиции p ba.remove(i)
    Удаляет первое вхождение целого числа i из объекта ba ти
    па bytearray

    Запись и чтение двоичных данных
    347
    b.replace
    (x, y, n)
    Возвращает копию объекта b, в котором каждое (но не более
    n
    , если этот аргумент определен) вхождение объекта x типа bytes
    /bytearray замещается объектом y ba.reverse()
    Переставляет в памяти элементы объекта ba типа bytearray в обратном порядке b.split(x, n)
    Возвращает список объектов типа bytes, выполняя разбие
    ние объекта b не более чем n раз по содержимому объекта x.
    Если число n не задано, разбиение выполняется по всем най
    денным вхождениям объекта x. Если объект x не задан, раз
    биение выполняется по пробельным символам. Для выпол
    нения разбиения строки, начиная с правого края, исполь
    зуйте метод rsplit.
    b.splitlines(f)
    Возвращает список строк, выполняя разбиение объекта b по символам перевода строки, удаляя их, если в аргументе f не задано значение True b.startswith
    (x start,
    end)
    Возвращает True, если объект b типа bytes/bytearray (или срез b[start:end]) начинается содержимым объекта x типа bytes
    /bytearray или содержимым любого объекта x типа bytes
    /bytearray, если x – это кортеж; в противном случае возвращает False b.strip(x)
    Возвращает копию объекта b, из которого удалены началь
    ные и завершающие пробельные символы (или байты, вхо
    дящие в объект x типа bytes/bytearray). Метод lstrip() вы
    полняет удаление только начальных символов (или байтов),
    а метод rstrip() – конечных b.swapcase()
    Возвращает копию объекта b, в котором все символы ASCII
    верхнего регистра преобразованы в символы нижнего реги
    стра, а все символы ASCII нижнего регистра – в символы верхнего регистра b.title()
    Возвращает копию объекта b, в котором первые символы
    ASCII каждого слова преобразованы в символы верхнего ре
    гистра, а все остальные символы ASCII – в символы нижне
    го регистра b.translate
    (bt, d)
    Возвращает копию объекта b, из которой удаляются все байты, входящие в объект d, а все остальные байты замеща
    ются байтами из объекта bt, причем индекс байта в объекте bt определяется значением байта в объекте b b.upper()
    Возвращает копию объекта b типа bytes/bytearray, в кото
    ром все символы ASCII приведены к верхнему регистру b.zfill(w)
    Возвращает копию объекта b, который, если его длина меньше величины w, дополняется слева символами нуля
    (байт 0x30) до длины w
    Синтаксис
    Описание

    348
    Глава 7. Работа с файлами
    Неформатированные двоичные данные
    с возможным сжатием
    Написание собственного программного кода для работы с двоичными данными обеспечивает нам полный контроль над форматом файла. Кро
    ме того, такой подход является более безопасным, чем использование модуля pickle, поскольку злонамеренные, недопустимые данные будут обрабатываться нашим программным кодом, а не интерпретатором.
    Разрабатывая собственные двоичные форматы файлов, совсем нелиш
    ним будет предусмотреть сигнатуру для идентификации типа файла и номер версии для идентификации версии используемого формата.
    Ниже приводятся определения, используемые в программе convertin
    cidents.py
    :
    MAGIC = b"AIB\x00"
    FORMAT_VERSION = b"\x00\x01"
    Мы использовали четыре байта для сигнатуры и два байта для обозна
    чения версии. Порядок следования байтов в машинном слове не имеет значения, потому что будут записываться отдельные байты, а не це
    лые числа в байтовом представлении, то есть последовательность бай
    тов будет одна и та же на любой аппаратной архитектуре.
    Для записи и чтения двоичных данных вручную нам необходимо неко
    торое средство преобразования объектов Python в соответствующее двоичное представление и средство преобразования двоичных данных в объекты Python. Большая часть необходимых нам функциональных возможностей предоставляется модулем struct, краткое описание ко
    торого приводится во врезке «Модуль struct» (стр. 349), и типами дан
    ных bytes и bytearray, краткое описание которых приводится во врезке
    «Типы данных bytes и bytearray» (стр. 344).
    К сожалению, модуль struct может обрабатывать строки только опре
    деленной длины, тогда как в нашем случае идентификаторы отчетов,
    названия аэропортов, типы самолетов и текст комментариев могут быть представлены строками переменной длины. Чтобы удовлетво
    рить требования модуля struct, мы создали функцию pack_string(), ко
    торая принимает строку и возвращает объект bytes, состоящий из двух компонентов: первый – это целое число, определяющее длину строки,
    и второй – последовательность байтов в кодировке UTF8, представ
    ляющих текст строки.
    Так как функция pack_string() будет вызываться только внутри функции export_binary(), мы поместили опреде
    ление pack_string() внутрь функции export_binary(). Это означает, что функция pack_string() будет недоступна за пределами функции export_binary(), и указывает, что это всего лишь локальная, вспомогательная функция. Ниже
    Локальные функции, стр. 409

    Запись и чтение двоичных данных
    349
    приводится начало функции export_binary() и полное определение функции pack_string():
    def export_binary(self, filename, compress=False):
    def pack_string(string):
    data = string.encode("utf8")
    format = "
    1   ...   36   37   38   39   40   41   42   43   ...   74


    написать администратору сайта