Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Глава 11. Модули, пакеты и программы
Дальше: Глава 13. Календари и часы

Часть II. Python на практике

Глава 12. Обрабатываем данные

Если достаточно долго мучить данные, они признаются [в чем угодно].

Рональд Коуз

До сих пор мы в основном говорили о самом языке Python — его типах данных, структурах кода, синтаксисе и т.д. Остальная часть книги посвящена применению Python для решения реальных задач.

В текущей главе вы познакомитесь со многими практическими приемами для управления данными: в мире баз данных это иногда называют манипулированием данными или ETL (extract/transform/load, то есть «извлечение/преобразование/загрузка»). И хоть в книгах по программированию данная тема обычно не рассматривается отдельно, программистам все равно приходится тратить много времени на то, чтобы привести данные в нужную для своих целей форму.

За последние несколько лет специальность, связанная с «наукой о данных», стала очень популяр­ной. В статье Harvard Business Review профессия специалиста, работающего с данными, названа «самой сексуальной в XXI веке». Если речь о востребованности и достойной оплате, тогда ладно, но и рутины в этой профессии более чем достаточно. Наука о данных выходит за пределы обычных требований ETL к базам данных и зачастую включает в себя машинное обучение — для того чтобы можно было находить идеи, недоступные человеческому взору. Я начну с рассмотрения простых форматов данных, а затем перейду к самым полезным инструментам науки о данных.

Форматы данных делятся на две категории: текстовые и бинарные. Строки в Python используются для текстовых данных, и как раз в этой главе содержится информация о строках, которую мы пропустили ранее:

символы Unicode;

регулярные выражения.

Затем мы перейдем к бинарным данным и рассмотрим еще два типа, встроенных в Python:

bytes для неизменяемых восьмибитных значений;

bytearrays для изменяемых.

Текстовые строки: Unicode

С основами строк в Python вы познакомились в главе 5. Пришло время более по­дробно рассмотреть формат Unicode.

Строки в Python 3 представляют собой последовательности символов Unicode, а не массивы байтов. Это, безусловно, самое значительное изменение в языке по сравнению с Python 2.

Все текстовые примеры до сих пор имели формат ASCII (American Standard Code for Information Interchange). Этот формат был определен в 1960-х годах, когда компьютеры были размером с холодильник и считали лишь немногим лучше его.

Основной единицей хранения информации был байт, который мог хранить 256 уникальных значений в своих 8 битах. По разным причинам формат ASCII использовал только 7 бит (128 уникальных значений): 26 символов верхнего регистра, 26 символов нижнего регистра, десять цифр, некоторые знаки препинания, символы пробела и непечатаемые символы.

Однако в мире существует больше букв, чем предоставляет формат ASCII. Вы могли заказать в кафе хот-дог, но не Gewu..rztraminer. Предпринималось множество попыток вместить больше букв и символов в 8 доступных бит, и время от времени вы будете сталкиваться с такими форматами. Вот некоторые из них:

Latin-1 или ISO 8859-1;

Windows code page 1252.

Каждый из этих форматов использует все 8 бит, но даже их бывает недостаточно, особенно если нужно воспользоваться неевропейскими языками. Unicode — это действующий международный стандарт, определяющий символы всех языков мира плюс математические и другие символы. Еще и эмодзи!

Unicode предоставляет уникальный номер каждому символу, независимо от платформы, программы и языка.

Консорциум Unicode

Страница Unicode Code Charts () содержит ссылки на все определенные на данный момент наборы символов с изображениями. В последней версии (12.0) определяется более 137 000 символов, каждый из которых имеет уникальное имя и идентификационный номер. Python 3.8 может работать со всеми этими символами. Они разбиты на восьмибитные наборы, которые называются плоскостями. Первые 256 плоскостей называются основными многоязычными уровнями. Обратитесь к странице о плоскостях в «Википедии» ()), чтобы получить более подробную информацию.

Строки формата Unicode в Python 3

Если вы знаете Unicode ID или название символа, то можете использовать его в строке Python. Вот несколько примеров.

Символ \u и расположенные за ним четыре шестнадцатеричных числа определяют символ, находящийся в одной из 256 многоязычных плоскостей Unicode. Два первых числа являются номером плоскости (от 00 до FF), а следующие два — индексом символа внутри плоскости. Плоскость с номером 00 — это старый добрый формат ASCII, и позиции символов в нем такие же, как и в ASCII.

• Для символов более высоких плоскостей нужно больше битов. Управляющая последовательность для них выглядит как \U: за ней следуют восемь шестнадцатеричных символов, крайний слева из которых должен быть равен 0.

Для всех символов конструкция \N{имя} позволяет указать символ с помощью его стандартного имени. Имена перечислены по адресу .

Модуль unicodedata содержит функции, которые преобразуют символы в обоих направлениях:

lookup() принимает не зависящее от регистра имя и возвращает символ Unicode;

name() принимает символ Unicode и возвращает его имя в верхнем регистре.

В следующем примере мы напишем проверочную функцию, которая принимает символ Unicode, ищет его имя, а затем ищет символ, соответствующий полученному имени (он должен совпасть с оригинальным):

>>> def unicode_test(value):

...     import unicodedata

...     name = unicodedata.name(value)

...     value2 = unicodedata.lookup(name)

...     print('value="%s", name="%s", value2="%s"' % (value, name, value2))

...

Попробуем проверить несколько символов, начиная с простой буквы формата ASCII:

>>> unicode_test('A')

value="A", name="LATIN CAPITAL LETTER A", value2="A"

Знак препинания, доступный в ASCII:

>>> unicode_test('$')

value="$", name="DOLLAR SIGN", value2="$"

Символ валюты из Unicode:

>>>    unicode_test('\u00a2')

value="¢", name="CENT SIGN", value2="¢"

Еще один символ валюты из Unicode:

>>>    unicode_test('\u20ac')

value="€", name="EURO SIGN", value2="€"

Единственная проблема, с которой вы можете столкнуться, — это ограничения на шрифт для отображения текста. Немногие шрифты имеют изображения для всех символов Unicode — в таком случае вместо отсутствующих символов отображается символ-заполнитель. Например, так выглядит символ Unicode SNOWMAN, содержащийся в пиктографических шрифтах:

>>>    unicode_test('\u2603')

value="snow.GIF", name="SNOWMAN", value2="snow.GIF"

Предположим, мы хотим сохранить в строке слово café. Одно из решений состоит в том, чтобы скопировать его из файла или с сайта и понадеяться, что это сработает:

>>> place = 'café'

>>> place

'café'

Это сработало, поскольку я скопировал слово из источника, использующего кодировку UTF-8 (которую мы рассмотрим далее), и вставил его.

Как же нам указать, что последний символ — это é? Если вы посмотрите на индекс символа Е, то увидите, что имя EWITHACUTE,LATINSMALLLETTER имеет индекс 00Е9. Проверим функциями name() и lookup(), с которыми мы только что работали.

Сначала передадим код символа, чтобы получить его имя:

>>> unicodedata.name('\u00e9')

'LATIN SMALL LETTER E WITH ACUTE'

Теперь найдем код для заданного имени:

>>> unicodedata.lookup('E WITH ACUTE, LATIN SMALL LETTER')

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

KeyError: "undefined character name 'E WITH ACUTE, LATIN SMALL LETTER'"

109200.png

Имена, перечисленные в списке Unicode Character Name Index, были переформатированы для удобства отображения. Для того чтобы преобразовать их в насто­ящие имена символов Unicode (которые используются в Python), удалите запятую и переместите ту часть имени, которая находится после нее, в самое начало. Соответственно, в нашем примере E WITH ACUTE, LATIN SMALL LETTER нужно изменить на LATIN SMALL LETTER E WITH ACUTE:

>>> unicodedata.lookup('LATIN SMALL LETTER E WITH ACUTE')

'é'

Теперь мы можем указать символ é и по коду, и по имени:

>>> place = 'caf\u00e9'

>>> place

'café'

>>> place = 'caf\N{LATIN SMALL LETTER E WITH ACUTE}'

>>> place

'café'

В предыдущем фрагменте вы вставили символ é непосредственно в строку, но также строку можно построить, добавив:

>>> u_umlaut = '\N{LATIN SMALL LETTER U WITH DIAERESIS}'

>>> u_umlaut

'ú'

>>> drink = 'Gew' + u_umlaut + 'rztraminer'

>>> print('Now I can finally have my', drink, 'in a', place)

Now I can finally have my Gewürztraminer in a café

Строковая функция len() считает количество символов в кодировке Unicode, а не байты:

>>> len('$')

1

>>>    len('\U0001f47b')

1

109269.png

Зная числовой идентификатор символа в Unicode, можно использовать стандартные функции ord() и chr() для того, чтобы быстро выполнять преобразования между целочисленными идентификаторами и односимвольными строками Unicode:

>>> chr(233)

'é'

>>> chr(0xe9)

'é'

>>> chr(0x1fc6)

'ῆ'

Кодирование и декодирование с помощью кодировки UTF-8

Вам не нужно волноваться о том, как Python хранит каждый символ Unicode, когда вы выполняете обычную обработку строки.

Но когда вы обмениваетесь данными с внешним миром, вам может понадобиться следующее:

способ закодировать строку в байты;

способ декодировать байты обратно в строку.

Если бы в Unicode было менее 65 536 символов, мы могли бы хранить ID каждого из них в 2 байтах. Однако символов больше, и если кодировать каждый ID с по­мощью 3 или 4 байт, объем памяти и дискового пространства, необходимых для обычных текстовых строк, увеличится в четыре раза.

Кен Томпсон и Роб Пайк, чьи имена знакомы работающим с Unix, однажды вечером на салфетке в одной из столовых Нью-Джерси разработали UTF-8 — динамическую схему кодирования. Она использует для символа Unicode от 1 до 4 байт:

1 байт для ASCII;

• 2 байта для большинства языков, основанных на латинице (но не кириллице);

• 3 байта для других основных языков;

4 байта для остальных языков, включая некоторые азиатские языки и символы.

UTF-8 — это стандартная текстовая кодировка для Python, Linux и HTML. Она охватывает множество символов, работает быстро и хорошо. Гораздо удобнее работать с кодировкой UTF-8, чем постоянно переключаться с одной кодировки на другую.

109345.png

Если вы создаете строку Python путем копирования и вставки из другого источ­ника, например такого, как веб-страница, убедитесь, что источник был закодирован с помощью UTF-8. Нередко может оказаться, что текст был зашифрован с помощью кодировок Latin-1 или Windows 1252, а это при копировании в строку Python вызывает генерацию исключений из-за некорректной последовательности байтов.

Кодирование

Вы кодируете строку байтами. Первый аргумент строковой функции encode() — это имя кодировки. Возможные варианты представлены в табл. 12.1.

Таблица 12.1. Кодировки

Имя кодировки

Описание

'ascii'

Старая добрая семибитная кодировка ASCII

'utf-8'

Восьмибитная кодировка переменной длины, самый предпочтительный вариант в большинстве случаев

'latin-1'

Также известна как ISO 8859-1

'cp-1252'

Стандартная кодировка Windows

'unicode-escape'

Буквенный формат Python Unicode, выглядит как \uxxxx или \Uxxxxxxxx

С помощью кодировки UTF-8 вы можете закодировать все что угодно. Присвоим строку Unicode '\u2603' переменной snowman:

>>> snowman = '\u2603'

snowman — это строка Python Unicode, содержащая один символ независимо от того, сколько байтов может потребоваться для ее сохранения:

>>> len(snowman)

1

Теперь закодируем этот символ последовательностью байтов:

>>> ds = snowman.encode('utf-8')

Как я упоминал ранее, кодировка UTF-8 имеет переменную длину. В данном случае было использовано три байта для того, чтобы закодировать один символ snowman:

>>> len(ds)

3

>>> ds

b'\xe2\x98\x83'

Функция len() возвращает число байтов — 3, поскольку ds является переменной bytes.

Вы можете использовать отличные от UTF-8 кодировки, но тогда вы получите ошибку, если строка Unicode не сможет быть обработана другой кодировкой. Напри­мер, если вы используете кодировку ascii, произойдет сбой, если только вы не предоставите строку, состоящую из корректных символов ASCII:

>>> ds = snowman.encode('ascii')

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

UnicodeEncodeError: 'ascii' codec can't encode character '\u2603'

in position 0: ordinal not in range(128)

Функция encode() принимает второй аргумент, который помогает избежать возникновения исключений, связанных с кодировкой. Его значение по умолчанию, как можно было видеть в предыдущем примере, равно 'strict': такое значение приводит к исключению UnicodeEncodeError, когда обнаруживается символ, не входящий в кодировку ASCII. Существуют и другие кодировки. Используйте значение 'ignore', чтобы исключить все, что не может быть закодировано:

>>> snowman.encode('ascii', 'ignore')

b''

Используйте значение 'replace', чтобы заменить неизвестные символы символами ?:

>>> snowman.encode('ascii', 'replace')

b'?'

Используйте значение 'backslashreplace', чтобы создать строку, содержащую символы Python Unicode, такие как unicode-escape:

>>> snowman.encode('ascii', 'backslashreplace')

b'\\u2603'

Вы можете использовать этот вариант, если вам нужна печатная версия escape-последовательности Unicode.

Используйте 'xmlcharrefreplace', чтобы создавать безопасные для HTML строки:

>>> snowman.encode('ascii', 'xmlcharrefreplace')

b'&#9731;'

Подробнее о преобразованиях HTML мы поговорим в подразделе «Сущности HTML» на с. 250.

Декодирование

Мы декодируем байтовые строки в текстовые строки Unicode. Когда мы получаем текст из какого-то внешнего источника (файлов, баз данных, сайтов, сетевых API и т.д.), он закодирован в виде байтовой строки. Самое сложное — узнать, какая кодировка была использована, чтобы можно было декодировать и получить строку Unicode.

Проблема в следующем: ничто в байтовой строке не говорит нам о том, какая кодировка была использована. Я уже упоминал опасность копирования/вставки с сайтов. Вероятно, и вы встречали сайты со странными символами в тех местах, где должны быть обычные символы ASCII.

Создадим строку Unicode, которая называется place и имеет значение 'café':

>>> place = 'caf\u00e9'

>>> place

'café'

>>> type(place)

<class 'str'>

Закодируем ее в формат UTF-8 с помощью переменной bytes, которая называется place_bytes:

>>> place_bytes = place.encode('utf-8')

>>> place_bytes

b'caf\xc3\xa9'

>>> type(place_bytes)

<class 'bytes'>

Обратите внимание на то, что переменная place_bytes содержит 5 байт. Первые три такие же, как в ASCII (преимущество UTF-8), а последние два кодируют символ 'é'. Теперь декодируем эту байтовую строку обратно в строку Unicode:

>>> place2 = place_bytes.decode('utf-8')

>>> place2

'café'

Это сработало, поскольку мы закодировали и декодировали строку с помощью кодировки UTF-8. Что, если бы мы указали декодировать ее с помощью какой-нибудь другой кодировки?

>>> place3 = place_bytes.decode('ascii')

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3:

ordinal not in range(128)

Декодер ASCII сгенерировал исключение, поскольку байтовое значение 0xc3 некорректно в ASCII. Существуют и другие восьмибитные кодировки, где значения между 128 (80 в шестнадцатеричной системе) и 255 (FF в шестнадцатеричной системе) корректны, но не совпадают со значениями UTF-8:

>>> place4 = place_bytes.decode('latin-1')

>>> place4

'café'

>>> place5 = place_bytes.decode('windows-1252')

>>> place5

'café'

Ох.

Мораль этой истории: по возможности используйте кодировку UTF-8. Она работает и поддерживается везде, она способна выразить любой символ Unicode, и с ее помощью можно быстро кодировать и декодировать.

109350.png

То, что вы можете указать любой символ Unicode, не значит, что компьютер сможет их все отобразить. Это зависит от шрифта, который вы используете, — вполне вероятно, в результате вы увидите изображение-заполнитель или вовсе ничего. Компания Apple создала шрифт Last Resort Font () для Unicode Consortium и использует его в своих операционных системах. На странице в «Википедии» можно найти более подробную информацию. Существует также шрифт Unifont (), содержащий символы в диапазоне от \u0000 до \uffff, а также некоторые другие.

Сущности HTML

В Python 3.4 появился еще один способ выполнять преобразования в Unicode и обратно — с помощью символов-мнемоников HTML. Этот подход может быть проще, чем поиск имен Unicode, особенно если вы работаете в Интернете:

>>> import html

>>> html.unescape("&egrave;")

'è'

Такое преобразование также подходит для мнемоников, пронумерованных как в десятичной, так и в шестнадцатеричной системах счисления:

>>> import html

>>> html.unescape("&#233;")

'é'

>>> html.unescape("&#xe9;")

'é'

Вы даже можете импортировать именованную сущность, содержащую соответствия, как словарь, и выполнить преобразование самостоятельно. Отбросьте начальный символ '&' в ключе словаря (можно также отбросить и последний символ ';': кажется, сработает в обоих случаях):

>>> from html.entities import html5

>>> html5["egrave"]

'è'

>>> html5["egrave;"]

'è'

Чтобы выполнить обратное преобразование (из одного символа Python Unicode в символ-мнемоник HTML), сначала нужно получить десятичное значение символа с помощью функции ord():

>>> import html

>>> char = '\u00e9'

>>> dec_value = ord(char)

>>> html.entities.codepoint2name[dec_value]

'eacute'

Для строк Unicode, содержащих больше одного символа, используйте следующее преобразование, состоящее из двух этапов:

>>> place = 'caf\u00e9'

>>> byte_value = place.encode('ascii', 'xmlcharrefreplace')

>>> byte_value

b'caf&#233;'

>>> byte_value.decode()

'caf&#233;'

Выражение place.encode('ascii','xmlcharrefreplace') вернуло символы ASCII, приведенные к типу bytes (поскольку они были закодированы). Следующее выражение byte_value.decode() необходимо для того, чтобы преобразовать переменную byte_value в строку, совместимую с HTML.

Нормализация

Некоторые символы Unicode могут быть представлены более чем одним кодом Unicode. Они выглядят одинаково, но их сравнение даст отрицательный результат, поскольку они содержат разные последовательности байтов. Например, возьмем символ с акутом 'é' в слове 'café'. Создадим односимвольную 'é' несколькими способами:

>>> eacute1 = 'é'                              # UTF-8, скопирован

>>> eacute2 = '\u00e9'                         # код Unicode

>>> eacute3 = \                                # имя Unicode

...     '\N{LATIN SMALL LETTER E WITH ACUTE}'

>>> eacute4 = chr(233)                # десятичное байтовое значение

>>> eacute5 = chr(0xe9)               # шестнадцатеричное байтовое значение

>>> eacute1, eacute2, eacute3, eacute4, eacute5

('é', 'é', 'é', 'é', 'é')

>>> eacute1 == eacute2 == eacute3 == eacute4 == eacute5

True

Выполним несколько проверок:

>>> import unicodedata

>>> unicodedata.name(eacute1)

'LATIN SMALL LETTER E WITH ACUTE'

>>> ord(eacute1)             # как десятичное целое число

233

>>> 0xe9                     # шестнадцатеричное целое число Unicode

233

Теперь создадим символ e с акутом, объединив простой символ e и акут:

>>> eacute_combined1 = "e\u0301"

>>> eacute_combined2 = "e\N{COMBINING ACUTE ACCENT}"

>>> eacute_combined3 = "e" + "\u0301"

>>> eacute_combined1, eacute_combined2, eacute_combined3

('é', 'é', 'é'))

>>> eacute_combined1 == eacute_combined2 == eacute_combined3

True

>>> len(eacute_combined1)

2

Мы создали символ Unicode из двух других, и он выглядит так же, как и оригинальный символ 'é'. Но, как говорят на «Улице Сезам», один из них не похож на другой:

>>> eacute1 == eacute_combined1

False

Если у вас две разные строки Unicode, полученные из разных источников — одна использует eacute1, а вторая eacute_combined1, — выглядеть они будут одинаково, но поведут себя по-разному.

Исправить это можно с помощью функции normalize(), которая содержится в модуле unicodedata:

>>> import unicodedata

>>> eacute_normalized = unicodedata.normalize('NFC', eacute_combined1)

>>> len(eacute_normalized)

1

>>> eacute_normalized == eacute1

True

>>> unicodedata.name(eacute_normalized)

'LATIN SMALL LETTER E WITH ACUTE'

'NFC' означает normal form, composed (нормальная форма, составной символ).

Подробная информация

Если хотите узнать больше, эти ссылки будут вам полезны:

Unicode HOWTO ();

• Pragmatic Unicode ();

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) ().

Текстовые строки: регулярные выражения

В главе 5 мы затронули операции со строками. Вооружившись этой вводной информацией, вы, вероятно, использовали простые «подстановочные» шаблоны в командной строке, такие как команда UNIX ls*.py, что означает перечисление всех имен файлов, заканчивающихся на .py.

Пришло время рассмотреть более сложный механизм проверки на совпадение с шаблоном — регулярные выражения. Этот механизм поставляется в стандартном модуле re, который мы импортируем. Вы определяете строковый шаблон, совпадения для которого вам нужно найти, и строку-источник, в которой следует выполнить поиск. Простой пример использования выглядит так:

>>> import re

>>> result = re.match('You', 'Young Frankenstein')

В этом примере строка 'You' является искомым шаблоном, а 'YoungFrankenstein' — источником, строкой, которую вы хотите проверить. Функция match() проверяет, начинается ли источник с шаблона.

Для более сложных проверок вам нужно скомпилировать шаблон, чтобы ускорить поиск:

>>> import re

>>> youpattern = re.compile('You')

Далее вы можете выполнить проверку с помощью скомпилированного шаблона:

>>> import re

>>> result = youpattern.match('Young Frankenstein')

109418.png

Поскольку в Python с этим часто приходится иметь дело, напомню еще раз: функция match() ищет строку-шаблон только в начале строки-источника, а функция search() ищет шаблон в любом месте источника.

Функция match() — не единственный способ сравнить шаблон и источник, существует еще несколько методов (каждый из них мы рассмотрим в следующих подразделах):

search() возвращает первое совпадение, если таковое имеется;

• findall() возвращает список всех непересекающихся совпадений, если таковые имеются;

• split() разбивает источник на совпадения с шаблоном и возвращает список всех фрагментов строки;

sub() принимает аргумент для замены и заменяет все части источника, совпавшие с шаблоном, на значение этого аргумента.

109486.png

В большей части примеров регулярных выражений, показанных здесь, используется кодировка ASCII, но строковые функции Python, включая функции для работы с регулярными выражениями, работают с любой строкой Python и любыми символами из кодировки Unicode.

Ищем точное начальное совпадение с помощью функции match()

Начинается ли строка 'YoungFrankenstein' со слова 'You'? Рассмотрим пример кода с комментариями:

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.match('You', source)  # функция начинает работать с начала источника

>>> if m:    # функция возвращает объект; делайте это, чтобы увидеть, что совпало

...     print(m.group())

...

You

>>> m = re.match('^You', source)  # якорь в начале строки делает то же самое

>>> if m:

...     print(m.group())

...

You

А что насчет 'Frank'?

>>> m = re.match('Frank', source)

>>> if m:

...     print(m.group())

...

В этот раз функция match() не вернула ничего, и оператор if не запустил оператор print.

Как я упоминал в разделе «Новое: I am the Walrus» на с. 89, в Python 3.8 этот пример можно сократить с помощью так называемого оператора-«моржа»:

>>> import re

>>> source = 'Young Frankenstein'

>>> if m := re.match('Frank', source):

...     print(m.group())

...

Теперь используем функцию search() для того, чтобы найти шаблон Frank в любом месте источника:

>>> import re

>>> source = 'Young Frankenstein

>>> m = re.search('Frank', source)

>>> if m:

...      print(m.group())

...

Frank

Изменим шаблон и попробуем найти первое совпадение с помощью функции match():

>>> m = re.match('.*Frank', source)

>>> if m:  # функция match возвращает объект

...     print(m.group())

...

Young Frank

Кратко объясню, как работает наш новый шаблон '.*Frank':

символ . означает любой отдельный символ;

• символ * означает ноль или более предыдущих элементов. Если объединить символы .*, они будут означать любое количество символов (даже ноль);

Frank — это фраза, которую мы хотим найти в любом месте строки.

Функция match() вернула строку, в которой нашлось совпадение с шаблоном .*Frank:'YoungFrank'.

Ищем первое совпадение с помощью функции search()

Вы можете использовать функцию search(), чтобы найти шаблон 'Frank' в любом месте строки-источника 'YoungFrankenstein', не прибегая к использованию символа подстановки .*:

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.search('Frank', source)

>>> if m:    # функция search возвращает объект

...     print(m.group())

...

Frank

Ищем все совпадения, используя функцию findall()

В предыдущих примерах мы искали только одно совпадение. Но что, если нужно узнать, сколько раз строка, содержащая один символ n, встречается в строке-источнике?

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.findall('n', source)

>>> m   # findall returns a list

['n', 'n', 'n', 'n']

>>> print('Found', len(m), 'matches')

Found 4 matches

А что насчет строки 'n', за которой следует любой символ?

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.findall('n.', source)

>>> m

['ng', 'nk', 'ns']

Обратите внимание на то, что в совпадения не была записана последняя строка 'n'. Мы должны указать, что символ после 'n' является опциональным, с помощью ?:

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.findall('n.?', source)

>>> m

['ng', 'nk', 'ns', 'n']

Разбиваем совпадения с помощью функции split()

В следующем примере показано, как разбить строку на список с помощью шаблона, а не простой строки:

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.split('n', source)

>>> m    # функция split возвращает список

['You', 'g Fra', 'ke', 'stei', '']

Заменяем совпадения с помощью функции sub()

Этот метод похож на метод replace(), но предназначен не для простых строк, а для шаблонов:

>>> import re

>>> source = 'Young Frankenstein'

>>> m = re.sub('n', '?', source)

>>> m   # sub returns a string

'You?g Fra?ke?stei?'

Шаблоны: специальные символы

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

Теперь, когда вы знаете о нужных функциях (match(), search(), findall() и sub()), рассмотрим детали построения регулярных выражений. Создаваемые вами шаблоны подойдут к любой из этих функций.

Самые простые знаки вы уже видели:

совпадения с любыми неспециальными символами;

• любой отдельный символ, кроме \n, — это символ .;

• любое число включений предыдущего символа, включая 0, — это символ *;

опциональное значение (0 или 1) включений предыдущего символа — это символ ?.

Специальные символы показаны в табл. 12.2.

Таблица 12.2. Специальные символы

Шаблон

Совпадения

\d

Цифровой символ

\D

Нецифровой символ

\w

Буквенный или цифровой символ или знак подчеркивания

\W

Любой символ, кроме буквенного или цифрового символа или знака подчеркивания

\s

Пробельный символ

\S

Непробельный символ

\b

Граница слова

\B

Не граница слова

Модуль string содержит заранее определенные строковые константы, которые можно использовать для тестирования. Попробуем применить константу printable: она содержит 100 печатных символов ASCII, включая буквы в обоих регистрах, цифры, пробелы и знаки пунктуации:

>>> import string

>>> printable = string.printable

>>> len(printable)

100

>>>    printable[0:50]

'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN'

>>>    printable[50:]

'OPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'

Какие символы строки printable являются цифрами?

>>> re.findall('\d', printable)

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

Какие символы являются цифрами, буквами и нижним подчеркиванием?

>>> re.findall('\w', printable)

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b',

'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',

'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',

'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',

'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',

'Y', 'Z', '_']

Какие символы являются пробелами?

>>> re.findall('\s', printable)

[' ', '\t', '\n', '\r', '\x0b', '\x0c']

Перед вами пробелы в следующем порядке: простой пробел, табуляция, возврат каретки, вертикальная табуляция и перевод страницы.

Регулярные выражения не ограничиваются символами ASCII. Шаблон \d совпадает со всем, что в кодировке Unicode считается цифрой, а не только с символами ASCII от 0 до 9. Добавим две не ASCII-буквы в нижнем регистре из FileFormat.info ().

В этой проверке мы добавим туда следующие символы:

три буквы ASCII;

• три знака препинания, которые не должны совпасть с шаблоном \w;

• символ Unicode LATIN SMALL LETTER E WITH CIRCUMFLEX (\u00ea);

• символ Unicode LATIN SMALL LETTER E WITH BREVE (\u0115):

>>> x = 'abc' + '-/*' + '\u00ea' + '\u0115'

Как и ожидалось, шаблон нашел только буквы:

>>> re.findall('\w', x)

['a', 'b', 'c', 'ê', 'ĕ'']

Шаблоны: использование спецификаторов

Теперь сделаем «пунктуационную пиццу», используя основные спецификаторы шаблонов для регулярных выражений, показанные в табл. 12.3.

В этой таблице expr и другие слова, выделенные курсивом, означают любое корректное регулярное выражение.

Таблица 12.3. Спецификаторы шаблонов

Шаблон

Совпадения

abc

Буквосочетание abc

(expr)

expr

expr1 | expr2

expr1 или expr2

.

Любой символ, кроме \n

^

Начало строки источника

$

Конец строки источника

prev ?

Ноль или одно включение prev

prev *

Ноль или больше включений prev, максимальное количество

prev *?

Ноль или больше включений prev, минимальное количество

prev +

Одно или больше включений prev, максимальное количество

prev +?

Одно или больше включений prev, минимальное количество

prev { m }

m последовательных включений prev

prev { m, n }

От m до n последовательных включений prev, максимальное количество

prev { m, n }?

От m до n последовательных включений prev, минимальное количество

[abc]

a, или b, или c (аналогично a|b|c)

[^ abc]

Не (a, или b, или c)

prev (?= next)

prev, если за ним следует next

prev (? ! next)

prev, если за ним не следует next

(?<= prev) next

next, если перед ним находится prev

(?<! prev) next

next, если перед ним не находится prev

У вас могло зарябить в глазах при попытке прочесть эти примеры. Для начала определим строку-источник:

>>> source = '''I wish I may, I wish I might

... Have a dish of fish tonight.'''

Теперь применим разные шаблоны и с помощью регулярных выражений попробуем найти их в строке-источнике.

109552.png

В следующих примерах в качестве шаблонов я использую простые строки в кавычках. Далее в этом подразделе я покажу, как необработанная строка-шаблон (она начинается с символа r перед первой кавычкой) позволяет избежать некоторых конфликтов между обычными escape-последовательностями в Python и регулярными выражениями. Из соображений безопасности первым аргументом во всех следующих примерах должна быть необработанная строка.

Для начала найдем в любом месте строку 'wish':

>>> re.findall('wish', source)

['wish', 'wish']

Далее найдем строки 'wish' или 'fish':

>>> re.findall('wish|fish', source)

['wish', 'wish', 'fish']

Найдем строку 'wish' в начале текста:

>>> re.findall('^wish', source)

[]

Найдем строку 'Iwish' в начале текста:

>>> re.findall('^I wish', source)

['I wish']

Найдем строку 'fish' в конце текста:

>>> re.findall('fish$', source)

[]

Наконец, найдем строку 'fishtonight.$' в конце текста:

>>> re.findall('fish tonight.$', source)

['fish tonight.']

Символы ^ и $ называются якорями: с помощью якоря ^ выполняется поиск в начале строки, а с помощью якоря $ — в конце. Сочетание .$ совпадает с любым символом в конце строки, включая точку, поэтому выражение сработало. Для обеспечения большей точности нужно создать escape-последовательность, чтобы найти именно точку:

>>> re.findall('fish tonight\.$', source)

['fish tonight.']

Начнем с поиска символов w или f, за которыми следует буквосочетание ish:

>>> re.findall('[wf]ish', source)

['wish', 'wish', 'fish']

Найдем одно или несколько сочетаний символов w, s и h:

>>> re.findall('[wsh]+', source)

['w', 'sh', 'w', 'sh', 'h', 'sh', 'sh', 'h']

Найдем сочетание ght, за которым следует любой символ, кроме буквенного или цифрового символа или знака подчеркивания:

>>> re.findall('ght\W', source)

['ght\n', 'ght.']

Найдем символ I, за которым следует сочетание wish:

>>> re.findall('I (?=wish)', source)

['I ', 'I ']

И наконец, сочетание wish, перед которым находится I:

>>> re.findall('(?<=I) wish', source)

[' wish', ' wish']

Существует несколько ситуаций, в которых правила шаблонов регулярных выражений конфликтуют с правилами для строк Python. Следующий шаблон должен совпасть с любым словом, которое начинается с fish:

>>> re.findall('\bfish', source)

[]

Почему этого не произошло? Как мы говорили в главе 5, Python использует специальные escape-последовательности для строк. Например, \b для строки означает возврат на шаг, но в мини-языке регулярных выражений эта последовательность означает начало слова. Избегайте случайного применения escape-последовательностей, используя неформатированные строки Python, когда определяете строку регулярного выражения. Всегда размещайте символ r перед строкой шаблона регулярного выражения, и escape-последовательности Python будут отключены, как показано здесь:

>>> re.findall(r'\bfish', source)

['fish']

Шаблоны: указываем способ вывода совпадения

При использовании функций match() или search() все совпадения можно получить из объекта результата m, вызвав функцию m.group(). Если вы заключите шаблон в круглые скобки, совпадения будут сохранены в отдельную группу, а кортеж, состоящий из них, окажется доступен благодаря вызову m.groups(). Так, как показано здесь:

>>> m = re.search(r'(. dish\b).*(\bfish)', source)

>>> m.group()

'a dish of fish'

>>> m.groups()

('a dish', 'fish')

Если вы используете этот шаблон (?P<имя>выр), он совпадет с выражением выр и сохранит совпадение в группе имя:

>>> m = re.search(r'(?P<DISH>. dish\b).*(?P<FISH>\bfish)', source)

>>> m.group()

'a dish of fish'

>>> m.groups()

('a dish', 'fish')

>>> m.group('DISH')

'a dish'

>>> m.group('FISH')

'fish'

Бинарные данные

Работать с текстовыми данными может быть трудно, но работать с бинарными может быть… интересно. Вам нужно знать о таких концепциях, как порядок следования байтов (как процессор вашего компьютера разбивает данные на байты) и знаковые биты для целых чисел. Вам может понадобиться закопаться в бинарные форматы файлов или сетевых пакетов, чтобы извлечь или даже изменить данные. В этом разделе я покажу вам основы работы с бинарными данными в Python.

bytes и bytearray

В Python 3 появились последовательности восьмибитных целых чисел с возможными значениями от 0 до 255. Они могут быть двух типов:

bytes неизменяем, как кортеж байтов;

bytearray изменяем, как список байтов.

Начнем мы с создания списка с именем blist. В этом примере создадим переменную типа bytes с именем the_bytes и переменную типа bytearray с именем the_byte_array:

>> blist = [1, 2, 3, 255]

>>> the_bytes = bytes(blist)

>>> the_bytes

b'\x01\x02\x03\xff'

>>> the_byte_array = bytearray(blist)

>>> the_byte_array

bytearray(b'\x01\x02\x03\xff')

109619.png

Представление значения типа bytes начинается с символа b и кавычки, за которыми следуют шестнадцатеричные последовательности из символов \x02 или ASCII, и заканчивается конструкция соответствующим символом кавычки. Python преобразует шестнадцатеричные последовательности или символы ASCII в небольшие целые числа, но показывает байтовые значения, корректно записанные с точки зрения кодировки ASCII:

>>> b'\x61'

b'a'

 

>>> b'\x01abc\xff'

b'\x01abc\xff'

В следующем примере показано, что вы не можете изменить переменную типа bytes:

>>> blist = [1, 2, 3, 255]

>>> the_bytes = bytes(blist)

>>> the_bytes[1] = 127

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

TypeError: 'bytes' object does not support item assignment

Но переменная типа bytearray легко изменяется:

>>> blist = [1, 2, 3, 255]

>>> the_byte_array = bytearray(blist)

>>> the_byte_array

bytearray(b'\x01\x02\x03\xff')

>>> the_byte_array[1] = 127

>>> the_byte_array

bytearray(b'\x01\x7f\x03\xff')

Каждая из этих переменных может содержать результат, состоящий из 256 элементов, имеющих значения от 0 до 255:

>>> the_bytes = bytes(range(0, 256))

>>> the_byte_array = bytearray(range(0, 256))

При выводе на экран содержимого переменных типа bytes или типа bytearray Python использует формат \xxx для непечатаемых байтов и их эквиваленты ASCII для печатаемых (и заменяет некоторые распространенные escape-последовательности, например \n на \x0a). Так выглядит на экране представление значения переменной the_bytes (переформатированное вручную для того, чтобы показать по 16 байт на строку):

>>> the_bytes

b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f

\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f

!"#$%&\'()*+,-./

0123456789:;<=>?

@ABCDEFGHIJKLMNO

PQRSTUVWXYZ[\\]^_

`abcdefghijklmno

pqrstuvwxyz{|}~\x7f

\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f

\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f

\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf

\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf

\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf

\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf

\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef

\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'

Это может показаться запутанным, поскольку перед нами байты (небольшие целые числа), а не символы.

Преобразуем бинарные данные с помощью модуля struct

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

Посмотрим, как он работает с данными из файла с расширением PNG (распространенным форматом изображений, использующимся наряду с GIF и JPEG). Напишем небольшую программу, которая извлекает ширину и высоту изображения из фрагмента данных PNG.

Мы будем использовать логотип издательства O’Reilly — изображение маленького долгопята с глазками-бусинами (рис. 12.1).

12_01.tif 

Рис. 12.1. Долгопят от издательства O’Reilly

Файл этого изображения с расширением PNG доступен в «Википедии» (). Мы не будем рассматривать чтение файлов вплоть до главы 14, поэтому я загрузил этот файл, написал небольшую программу для вывода его значений в байтах и просто распечатал значения первых 30 байт как значения переменной типа bytes с именем data для примера, размещенного ниже. (Специ­фикация формата PNG предполагает, что ширина и высота хранятся в первых 24 байтах, поэтому больше данных нам пока и не нужно.)

>>> import struct

>>> valid_png_header = b'\x89PNG\r\n\x1a\n'

>>> data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR' + \

...    b'\x00\x00\x00\x9a\x00\x00\x00\x8d\x08\x02\x00\x00\x00\xc0'

>>> if data[:8] == valid_png_header:

...    width, height = struct.unpack('>LL', data[16:24])

...    print('Valid PNG, width', width, 'height', height)

... else:

...    print('Not a valid PNG')

...

Valid PNG, width 154 height 141

Этот код делает следующее:

переменная data содержит первые 30 байт файла PNG. Для того чтобы раз­местить ее на странице, я объединил две байтовые строки с помощью операторов + и \;

• переменная valid_png_header содержит восьмибайтную последовательность, которая обозначает начало корректного PNG-файла;

значение переменной width извлекается из байтов 16–19, а переменной height — из байтов 20–23.

>LL — это строка формата, которая указывает функции unpack(), как интерпретировать входные последовательности байтов и преобразовывать их в типы данных Python. Рассмотрим ее детальнее:

символ > означает, что целые числа хранятся в формате big-endian (прямой порядок байтов);

каждый символ L определяет четырехбайтное целое число типа unsigned long.

Вы можете проверить значение каждого четырехбайтного набора напрямую:

>>> data[16:20]

b'\x00\x00\x00\x9a'

>>> data[20:24]0x9a

b'\x00\x00\x00\x8d'

У целых чисел с прямым порядком байтов (big-endian) главный байт располагается слева. Поскольку значения ширины и длины меньше 255, они умещаются в последний байт каждой последовательности. Вы можете убедиться в том, что эти шестнадцатеричные значения соответствуют ожидаемым десятичным значениям:

>>> 0x9a

154

>>> 0x8d

141

Если вы хотите произвести противоположное действие, то есть преобразовать данные Python в байты, используйте функцию pack() модуля struct:

>>> import struct

>>> struct.pack('>L', 154)

b'\x00\x00\x00\x9a'

>>> struct.pack('>L', 141)

b'\x00\x00\x00\x8d'

В табл. 12.4 и 12.5 показаны спецификаторы формата для функций pack() и unpack().

Спецификаторы порядка байтов (endian) располагаются первыми в строке формата.

Таблица 12.4. Спецификаторы порядка байтов

Спецификатор

Порядок байтов

<

Обратный порядок (Little endian)

>

Прямой порядок (Big endian)

Таблица 12.5. Спецификаторы формата

Спецификатор

Описание

Количество байтов

x

Пропустить байт

1

b

Знаковый байт

1

B

Беззнаковый байт

1

h

Знаковое короткое целое число

2

H

Беззнаковое короткое целое число

2

i

Знаковое целое число

4

I

Беззнаковое целое число

4

l

Знаковое длинное целое число

4

L

Беззнаковое длинное целое число

4

Q

Беззнаковое очень длинное целое число

8

f

Число с плавающей точкой

4

d

Число с плавающей точкой двойной точности

8

p

Счетчик и символы 1 + количество

1 + количество

s

Символы

количество

Спецификаторы типа следуют за символом, указывающим порядок байтов.

Любому спецификатору может предшествовать число, указывающее количество; запись 5B аналогична записи BBBBB.

Вы можете использовать префикс количества вместо конструкции >LL:

>>> struct.unpack('>2L', data[16:24])

(154, 141)

Мы использовали разделение data[16:24], чтобы получить интересующие нас байты напрямую. А могли бы использовать спецификатор x, чтобы пропустить ненужные нам части:

>>> struct.unpack('>16x2L6x', data)

(154, 141)

Эта строка означает:

использовать формат с прямым порядком байтов (>);

• пропустить 16 байт (16x);

• прочитать 8 байт — два беззнаковых длинных целых числа (2L);

пропустить последние 6 байт (6x).

Другие инструменты для работы с бинарными данными

Некоторые сторонние пакеты с открытым исходным кодом часто предлагают следующие более декларативные способы определения и извлечения бинарных данных:

bitstring ();

• construct ();

• hachoir ();

• binio (/);

Kaitai Struct (/).

В приложении Б содержатся инструкции о том, как загрузить и установить такие внешние пакеты. В следующем примере используем пакет construct. Вот все, что вам необходимо сделать:

$ pip install construct

Здесь показано, как можно извлечь измерения PNG из нашей строки байтов data с помощью пакета construct:

>>> from construct import Struct, Magic, UBInt32, Const, String

>>> # адаптировано из кода по адресу

>>> fmt = Struct('png',

...     Magic(b'\x89PNG\r\n\x1a\n'),

...     UBInt32('length'),

...     Const(String('type', 4), b'IHDR'),

...     UBInt32('width'),

...     UBInt32('height')

...     )

>>> data = b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR' + \

...    b'\x00\x00\x00\x9a\x00\x00\x00\x8d\x08\x02\x00\x00\x00\xc0'

>>> result = fmt.parse(data)

>>> print(result)

Container:

    length = 13

    type = b'IHDR'

    width = 154

    height = 141

>>> print(result.width, result.height)

154, 141

Преобразуем байты/строки с помощью модуля binascii

Стандартный модуль binascii содержит функции, которые позволяют вам конвертировать данные в бинарный вид и в различные представления строк: шестнадцатеричное (с основанием 16), с основанием 64, uuencoded и др. Например, в следующем фрагменте мы выведем на экран восьмибайтный заголовок PNG как последовательность шестнадцатеричных значений вместо комбинации символов ASCII и escape-последовательностей вида \xxx, которые Python использует для отображения байтовых переменных:

>>> import binascii

>>> valid_png_header = b'\x89PNG\r\n\x1a\n'

>>> print(binascii.hexlify(valid_png_header))

b'89504e470d0a1a0a'

В обратном направлении это тоже работает:

>>> print(binascii.unhexlify(b'89504e470d0a1a0a'))

b'\x89PNG\r\n\x1a\n'

Битовые операторы

Python предоставляет целочисленные операторы на уровне битов, аналогичные тем, что имеются в языке С. В табл. 12.6 собраны все операторы вместе с примерами для целочисленных переменных a (в десятичной системе счисления 5, в двоичной — 0b0101) и b (в десятичной системе счисления 1, в двоичной — 0b0001).

Таблица 12.6. Целочисленные операции для уровня битов

Оператор

Описание

Пример

Десятичный результат

Двоичный результат

&

Логическое И

a & b

1

0b0001

|

Логическое ИЛИ

a | b

5

0b0101

^

Исключающее ИЛИ

a ^ b

4

0b0100

~

Инверсия битов

~a

–6

Двоичное представление зависит от размера типа int

<<

Сдвиг влево

a << 1

10

0b1010

>>

Сдвиг вправо

a >> 1

2

0b0010

Эти операторы похожи на операторы для работы с множествами, показанные в главе 8. Оператор & возвращает биты, одинаковые в обоих аргументах, а оператор | возвращает биты, которые установлены в обоих аргументах. Оператор ^ возвращает биты, установленные в одном или в другом аргументе, но не в них обоих. Оператор ~ инвертирует порядок байтов в одном аргументе и также изменяет знак, поскольку старший бит целого числа указывает на его знак (1 означает «минус») в арифметике дополнительных кодов, которая используется во всех современных компьютерах. Операторы << и >> просто смещают биты влево или вправо. Сдвиг влево на один бит аналогичен умножению на 2, а сдвиг вправо — делению на 2.

Аналогия с ювелирными изделиями

Строки Unicode похожи на браслет с брелоками, а байты — на нитки с бусинами.

Читайте далее

Далее следует еще одна практическая глава — в ней показывается, как работать с датой и временем.

Упражнения

12.1. Создайте строку Unicode с именем mystery и присвойте ей значение '\U0001f4a9'. Выведите на экран значение строки mystery. Выведите на экран значение переменной mystery и ее имя Unicode.

12.2. Закодируйте строку mystery, на этот раз с использованием кодировки UTF-8, в переменную типа bytes с именем pop_bytes. Выведите на экран значение переменной pop_bytes.

12.3. Используя кодировку UTF-8, декодируйте переменную pop_bytes в строку pop_string. Выведите на экран значение переменной pop_string. Равно ли оно значению переменной mystery?

12.4. При работе с текстом могут пригодиться регулярные выражения. В следующем примере (пример 12.1) мы воспользуемся ими несколькими способами. Перед вами стихотворение Ode on the Mammoth Cheese, написанное Джеймсом Макинтайром в 1866 году во славу головки сыра весом 7000 фунтов, которая была изготовлена в Онтарио и отправлена в международное путешествие. Если не хотите самостоятельно перепечатывать это стихотворение, используйте свой любимый поисковик и скопируйте текст в программу. Или скопируйте стихотворение из проекта «Гутенберг» (). Назовите следующую строку mammoth:

Пример 12.1. mammoth.txt

We have seen thee, queen of cheese,

Lying quietly at your ease,

Gently fanned by evening breeze,

Thy fair form no flies dare seize.

 

All gaily dressed soon you'll go

To the great Provincial show,

To be admired by many a beau

In the city of Toronto.

 

Cows numerous as a swarm of bees,

Or as the leaves upon the trees,

It did require to make thee please,

And stand unrivalled, queen of cheese.

 

May you not receive a scar as

We have heard that Mr. Harris

Intends to send you off as far as

The great world's show at Paris.

 

Of the youth beware of these,

For some of them might rudely squeeze

And bite your cheek, then songs or glees

We could not sing, oh! queen of cheese.

 

We'rt thou suspended from balloon,

You'd cast a shade even at noon,

Folks would think it was the moon

About to fall and crush them soon.

12.5. Импортируйте модуль re, чтобы использовать функции регулярных выражений в Python. Примените функцию re.findall() для вывода на экран всех слов, начинающихся с буквы с.

12.6. Найдите все четырехбуквенные слова, которые начинаются с буквы c.

12.7. Найдите все слова, которые заканчиваются на букву r.

12.8. Найдите все слова, которые содержат три гласные подряд.

12.9. Используйте метод unhexlify для того, чтобы преобразовать шестнадцатеричную строку, созданную путем объединения двух строк для размещения на странице, в переменную типа bytes с именем gif:

'47494638396101000100800000000000ffffff21f9' +

'0401000000002c000000000100010000020144003b'

12.10. Байты, содержащиеся в переменной gif, определяют однопиксельный прозрачный GIF-файл. Этот формат является одним из самых распространенных. Корректный файл формата GIF начинается со строки GIF89a. Корректен ли этот файл?

12.11. Ширина GIF-файла в пикселах является шестнадцатибитным целым числом с обратным порядком байтов, которое начинается со смещения 6 байт. Высота имеет такой же размер и начинается со смещения 8 байт. Извлеките и выведите на экран эти значения для переменной gif. Равны ли они 1?

Название вина в Германии пишется через u.., но теряет эту букву в Эльзасе по пути во Францию.

Числа шестнадцатеричной системы счисления, содержащие символы от 0 до 9 и от A до F.

По адресу вы можете найти таблицу символов-мнемоников в HTML5.

Назад: Глава 11. Модули, пакеты и программы
Дальше: Глава 13. Календари и часы

dofermerdat
этот. породы кроликов с фотографиями мясныефермер ру форум рассадакрупнейшие сельскохозяйственные районы зарубежной европыкрс мясо