Строки — последовательности символов в кодировке Unicode, используемые для представления текстовых данных.
• Байты и массивы байтов — последовательности восьмибитных целых чисел, используемые для представления двоичных данных.
Текстовые строки
Текст является наиболее популярным типом данных для большинства читателей, поэтому мы начнем главу с рассмотрения мощных особенностей текстовых строк в Python.
Unicode
Все текстовые примеры, показанные в книге до этого момента, имели формат ASCII. Этот формат был определен в 1960-х годах, когда компьютеры были размером с холодильник и выполняли вычисления немного лучше последнего. Основной единицей хранения информации был байт, который мог хранить 256 уникальных значений в своих 8 битах. По разным причинам формат ASCII использовал только 7 бит (128 уникальных значений): 26 символов верхнего регистра, 26 символов нижнего регистра, 10 цифр, некоторые знаки препинания, символы пробела и непечатаемые символы.
К сожалению, в мире существует больше букв, чем предоставляет формат ASCII. Вы могли заказать в кафе хот-дог, но не Gewürztraminer (название этого вина в Германии пишется через ü, а во Франции — без него). Было предпринято множество попыток добавить больше букв и символов, и время от времени вы будете встречать их. Вот некоторые из них:
• Latin-1 или ISO 8859-1;
• Windows code page 1252.
Каждый из этих форматов использует все 8 бит, но даже этого недостаточно, особенно когда вам нужно воспользоваться неевропейскими языками. Unicode — это действующий международный стандарт, определяющий символы всех языков мира плюс математические и другие символы.
«Unicode предоставляет уникальный номер каждому символу независимо от платформы, программы и языка» (Консорциум Unicode).
Страница Unicode Code Charts (http://www.unicode.org/charts) содержит ссылки на все определенные на данный момент наборы символов с изображениями. В последней версии (6.2) определяется более 110 000 символов, каждый из которых имеет уникальное имя и идентификационный номер. Символы разбиты на восьмибитные наборы, которые называются плоскостями. Первые 256 плоскостей называются основными многоязычными уровнями. Обратитесь к странице о плоскостях в «Википедии» (http://bit.ly/unicode-plane), чтобы получить более подробную информацию.
Строки формата Unicode в Python 3Строки в Python 3 являются строками формата Unicode, а не массивом байтов. Одним разграничением между обычными байтовыми строками и строками в формате Unicode Python 3 значительно отличается от Python 2.
Если вы знаете Unicode ID или название символа, то можете использовать его в строке Python. Вот несколько примеров.
• Символ \u, за которым располагаются четыре шестнадцатеричных числа (числа шестнадцатеричной системы счисления, содержащие символы от 0 до 9 и от A до F), определяют символ, находящийся в одной из 256 многоязычных плоскостей Unicode. Первые два числа являются номером плоскости (от 00 до FF), а следующие два — индексом символа внутри плоскости. Плоскость с номером 00 — это старый добрый формат ASCII, и позиции символов в нем такие же, как и в ASCII.
• Для символов более высоких плоскостей нужно больше битов. Управляющая последовательность для них выглядит как \U, за которым следуют восемь шестнадцатеричных символов, крайний слева из них должен быть равен 0.
• Для всех символов конструкция \N{ имя } позволяет указать символ с помощью его стандартного имени. Имена перечислены по адресу http://www.unicode.org/charts/charindex.html.
Модуль 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="☃", name="SNOWMAN", value2="☃"
Предположим, мы хотим сохранить в строке слово café. Одно из решений состоит в том, чтобы скопировать его из файла или с сайта и понадеяться, что это сработает:
>>> place = 'café'
>>> place
'café'
Это сработало, поскольку я скопировал это слово из источника, использующего кодировку UTF-8 (с которой вы познакомитесь далее), и вставил его.
Как же нам указать, что последний символ — это «é»? Если вы посмотрите на индекс символа «Е», вы увидите, что имя E WITH ACUTE, LATIN SMALL LETTER имеет индекс 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 "", line 1, in
KeyError: "undefined character name 'E WITH ACUTE, LATIN SMALL LETTER'"
Имена, перечисленные в списке 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
Кодирование и декодирование с помощью кодировки UTF-8Вам не нужно волноваться о том, как Python хранит каждый символ Unicode, когда вы выполняете обычную обработку строки.
Но когда вы обмениваетесь данными с внешним миром, вам может понадобиться следующее:
• способ закодировать строку с помощью байтов;
• способ декодировать байты обратно в строку.
Если бы в Unicode было менее 64 000 символов, мы могли бы хранить ID каждого из них в двух байтах. К сожалению, символов больше. Мы могли бы кодировать каждый ID с помощью трех или четырех байтов, но это увеличило бы объем памяти и дискового пространства, необходимый для обычных текстовых строк, в три или четыре раза.
Кен Томпсон (Ken Thompson) и Роб Пайк (Rob Pike), чьи имена будут знакомы разработчикам на Unix, разработали UTF-8 — динамическую схему кодирования — однажды вечером на салфетке в одной из столовых Нью-Джерси. Она использует для символа Unicode от одного до четырех байтов:
• один байт для ASCII;
• два байта для большинства языков, основанных на латинице (но не кириллице);
• три байта для остальных простых языков;
• четыре байта для остальных языков, включая некоторые азиатские языки и символы.
UTF-8 — это стандартная текстовая кодировка для Python, Linux и HTML. Она охватывает множество символов, работает быстро и хорошо. Если вы используете кодировку UTF-8 в своем коде, жизнь станет гораздо проще, чем в том случае, если будете скакать от одной кодировки к другой.
Если вы создаете строку Python путем копирования символов из другого источника вроде веб-страницы и их вставки, убедитесь, что источник был закодирован с помощью UTF-8. Очень часто может оказаться, что текст был зашифрован с помощью кодировок Latin-1 или Windows 1252, что при копировании в строку Python вызовет генерацию исключений из-за некорректной последовательности байтов.
КодированиеВы кодируете строку байтами. Первый аргумент строковой функции encode() — это имя кодировки. Возможные варианты представлены в табл. 7.1.
Таблица 7.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 "", line 1, in
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'
Вы можете использовать этот вариант, если вам нужна печатаемая версия управляющей последовательности Unicode.
В следующем примере создаются строки символьных сущностей, которые вы можете встретить на веб-страницах:
>>> snowman.encode('ascii', 'xmlcharrefreplace')
b'☃'
ДекодированиеМы декодируем байтовые строки в строки Unicode. Когда мы получаем текст из какого-то внешнего источника (файлы, базы данных, сайты, сетевые API и т. д.), он закодирован в виде байтовой строки. Идея заключается в том, чтобы знать, какая кодировка была использована, чтобы мы могли ее декодировать и получить строку Unicode.
Проблема в следующем: никакая часть байтовой строки не говорит нам о том, какая была использована кодировка. Я уже упоминал опасности копирования/вставки с сайтов. Вы, возможно, посещали сайты, содержащие странные символы в том месте, где должны быть простые символы ASCII.
Создадим строку Unicode, которая называется place и имеет значение 'café':
>>> place = 'caf\u00e9'
>>> place
'café'
>>> type(place)
Закодируем ее в формат UTF-8 с помощью переменной bytes, которая называется place_bytes:
>>> place_bytes = place.encode('utf-8')
>>> place_bytes
b'caf\xc3\xa9'
>>> type(place_bytes)
Обратите внимание на то, что переменная place_bytes содержит пять байтов. Первые три похожи на 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 "", line 1, in
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 и быстро закодировать и декодировать.
Подробная информацияЕсли вы хотите узнать больше, вам могут помочь следующие ссылки:
• Unicode HOWTO (http://bit.ly/unicode-howto);
• Pragmatic Unicode (http://bit.ly/pragmatic-uni);
• The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) (http://bit.ly/jspolsky).
Формат
До этого момента мы просто игнорировали форматирование текста. В главе 2 были показаны несколько функций для выравнивания строк, а в примерах кода использовалась простая функция print() или даже вывод информации на экран поручался интерактивному интерпретатору. Но теперь мы рассмотрим, как интерполировать данные в строки — другими словами, разместить значения внутри строк, — применяя разные форматы. Вы можете использовать эту возможность, чтобы создавать отчеты и другие документы, для которых нужно задать определенный внешний вид.
Python предлагает два способа форматирования строк, их часто называют старым стилем и новым стилем. Оба стиля поддерживаются Python 2 и 3 (новый стиль появился в Python 2.6). Старый стиль проще, поэтому мы начнем с него.
Старый стиль с символом %Старый стиль форматирования строк имеет форму строка % данные. Внутри строки находятся интерполяционные последовательности. В табл. 7.2 показано, что самая простая последовательность — это символ %, за которым следует буква, представляющая тип данных, который должен быть отформатирован.
Таблица 7.2. Типы преобразования%s Строка %d Целое число в десятичной системе счисления %x Целое число в шестнадцатеричной системе счисления %o Целое число в восьмеричной системе счисления %f Число с плавающей точкой в десятичной системе счисления %e Число с плавающей точкой в шестнадцатеричной системе счисления %g Число с плавающей точкой в восьмеричной системе счисления %% Символ %
Далее мы рассмотрим несколько примеров. Сначала целое число:
>>> '%s' % 42
'42'
>>> '%d' % 42
'42'
>>> '%x' % 42
'2a'
>>> '%o' % 42
'52'
Число с плавающей точкой:
>>> '%s' % 7.03
'7.03'
>>> '%f' % 7.03
'7.030000'
>>> '%e' % 7.03
'7.030000e+00'
>>> '%g' % 7.03
'7.03'
Целое число и символ %:
>>> '%d%%' % 100
'100 %'
Интерполяция некоторых строк и целых чисел:
>>> actor = 'Richard Gere'
>>> cat = 'Chester'
>>> weight = 28
>>> "My wife's favorite actor is %s" % actor
"My wife's favorite actor is Richard Gere"
>>> "Our cat %s weighs %s pounds" % (cat, weight)
'Our cat Chester weighs 28 pounds'
Последовательность %s внутри строки означает, что в нее нужно интерполировать строку. Количество использованных символов % должно совпадать с количеством объектов, которые располагаются после %. Один объект вроде actor располагается сразу после символа %. Если таких объектов несколько, они должны быть сгруппированы в кортеж (нужно окружить их скобками и разделить запятыми) вроде (cat, weight).
Несмотря на то что переменная weight целочисленная, последовательность %s внутри строки преобразует ее в строку.
Вы можете добавить другие значения между % и определением типа, чтобы указать минимальную и максимальную ширину, выравнивание и заполнение символами.
Определим несколько переменных: целочисленную n, число с плавающей точкой f и строку s:
>>> n = 42
>>> f = 7.03
>>> s = 'string cheese'
Отформатируем их, используя ширину по умолчанию:
>>> '%d %f %s' % (n, f, s)
'42 7.030000 string cheese'
Установим минимальную длину поля, равную 10 символам, для каждой переменной и выровняем их по правому краю, заполняя неиспользованное место пробелами:
>>> '%10d %10f %10s' % (n, f, s)
'········42···7.030000 string cheese'
Используем ту же ширину поля, но выравнивание будет по левому краю:
>>> '%-10d %-10f %-10s' % (n, f, s)
'42·········7.030000···string cheese'
В этот раз укажем ту же длину поля, но максимальное количество символов будет равно 4, выровняем все по правому краю. Такая настройка обрезает строку и ограничивает число с плавающей точкой четырьмя цифрами после десятичной запятой:
>>> '%10.4d %10.4f %10.4s' % (n, f, s)
'······0042·····7.0300·······stri'
То же самое, но выравнивание по правому краю:
>>> '%.4d %.4f %.4s' % (n, f, s)
'0042 7.0300 stri'
Наконец, получим длину полей из аргументов, вместо того чтобы жестко ее закодировать:
>>> '%*.*d %*.*f %*.*s' % (10, 4, n, 10, 4, f, 10, 4, s)
'······0042·····7.0300·······stri'
Новый стиль форматирования с помощью символов {} и функции formatСтарый стиль форматирования все еще поддерживается. В Python 2, который остановился на версии 2.7, он будет поддерживаться всегда. Но если вы работаете с Python 3, рекомендуется применять новый стиль форматирования.
Простейший пример его использования показан здесь:
>>> '{} {} {}'.format(n, f, s)
'42 7.03 string cheese'
Аргументы старого стиля нужно предоставлять в порядке появления их заполнителей с символами % в оригинальной строке. С помощью нового стиля вы можете указывать любой порядок:
>>> '{2} {0} {1}'.format(f, s, n)
'42 7.03 string cheese'
Значение 0 относится к первому аргументу, f, 1 относится к строке s, а 2 — к последнему аргументу, целому числу n.
Аргументы могут являться словарем или именованными аргументами, а спецификаторы могут включать их имена:
>>> '{n} {f} {s}'.format(n=42, f=7.03, s='string cheese')
'42 7.03 string cheese'
В следующем примере попробуем объединить три наших значения в словарь, который выглядит так:
>>> d = {'n': 42, 'f': 7.03, 's': 'string cheese'}
В следующем примере {0} подразумевает весь словарь, а {1} — строку 'other', которая следует за словарем:
>>> '{0[n]} {0[f]} {0[s]} {1}'.format(d, 'other')
'42 7.03 string cheese other'
В этих примерах аргументы выводятся на экран с форматированием по умолчанию. Старый стиль позволяет указать спецификатор типа после символа %, а новый стиль — после:. Начнем с аргументов позиционирования:
>>> '{0:d} {1:f} {2:s}'.format(n, f, s)
'42 7.030000 string cheese'
В этом примере мы используем те же значения, но для именованных аргументов:
>>> '{n: d} {f: f} {s: s}'.format(n=42, f=7.03, s='string cheese')
'42 7.030000 string cheese'
Другие возможности (минимальная длина поля, максимальная ширина символов, смещение и т. д.) также поддерживаются.
Минимальная длина поля — 10, выравнивание по правому краю (по умолчанию):
>>> '{0:10d} {1:10f} {2:10s}'.format(n, f, s)
'········42···7.030000 string cheese'
То же, что и в предыдущем примере, но символы > делают выравнивание по правому краю более явным:
>>> '{0:>10d} {1:>10f} {2:>10s}'.format(n, f, s)
'········42···7.030000 string cheese'
Минимальная длина поля — 10, выравнивание по левому краю:
>>> '{0:<10d} {1:<10f} {2:<10s}'.format(n, f, s)
'42·········7.030000···string cheese'
Минимальная длина поля — 10, выравнивание по центру:
>>> '{0:^10d} {1:^10f} {2:^10s}'.format(n, f, s)
'····42······7.030000··string cheese'
Есть один момент, который отличает старый стиль от нового: значение точности (после десятичной запятой) все еще означает количество цифр после десятичной запятой для дробных чисел и максимальное число символов строки, но вы не можете использовать его для целых чисел:
>>> '{0:>10.4d} {1:>10.4f} {2:10.4s}'.format(n, f, s)
Traceback (most recent call last):
··File "", line 1, in
ValueError: Precision not allowed in integer format specifier
>>> '{0:>10d} {1:>10.4f} {2:>10.4s}'.format(n, f, s)
'········42·····7.0300·······stri'
Последняя опция — это символ-заполнитель. Если вы хотите заполнить поле вывода чем-то кроме пробелов, разместите этот символ сразу после двоеточия, но перед символами выравнивания (<, >, ^) или спецификатором ширины:
>>> '{0:!^20s}'.format('BIG SALE')
'!!!!!!BIG SALE!!!!!!'
Совпадение с регулярными выражениями
В главе 2 немного рассматривались операции со строками. Вооружившись этой промежуточной информацией, вы, возможно, использовали простые шаблоны в командной строке, содержащие символ подстановки, вроде ls *.py, что означает «перечислить все имена файлов, заканчивающиеся на. py».
Пришло время рассмотреть более сложный механизм проверки на совпадение с шаблоном — регулярные выражения. Этот механизм поставляется в стандартном модуле re, который мы импортируем. Вы определяете строковый шаблон, совпадения для которого вам нужно найти, и строку-источник, в которой следует выполнить поиск. Простой пример использования выглядит так:
result = re.match('You', 'Young Frankenstein')
В этом примере строка 'You' является шаблоном, а 'Young Frankenstein' — источником, строкой, которую вы хотите проверить. Функция match() проверяет, начинается ли источник с шаблона.
Для более сложных проверок вам нужно скомпилировать шаблон, чтобы ускорить поиск:
youpattern = re.compile('You')
Далее вы можете выполнить проверку с помощью скомпилированного шаблона:
result = youpattern.match('Young Frankenstein')
Функция match() — это не единственный способ сравнить шаблон и источник, существует еще несколько методов.
• search() возвращает первое совпадение, если таковое имеется.
• findall() возвращает список всех непересекающихся совпадений, если таковые имеются.
• split() разбивает источник на совпадения с шаблоном и возвращает список всех фрагментов строки.
• sub() принимает аргумент для замены и заменяет все части источника, совпавшие с шаблоном, на значение этого аргумента.
Точное совпадение с помощью функции match()Начинается ли строка 'Young Frankenstein' со слова '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. Как я говорил ранее, функция match() работает только в том случае, если шаблон находится в начале источника. Но функция search() ищет шаблон в любом месте источника:
>>> m = re.search('Frank', source)
>>> if m:
…······print(m.group())
…
Frank
Изменим шаблон:
>>> m = re.match('.*Frank', source)
>>> if m:··# match returns an object
…·····print(m.group())
…
Young Frank
Кратко объясню, как работает наш новый шаблон:
• символ. означает любой символ;
• символ * означает любое количество предыдущих элементов. Если объединить символы.*, они будут означать любое количество символов (даже ноль);
• 'Frank' — это фраза, которую мы хотим найти в любом месте строки.
Функция match() вернула строку, в которой нашлось совпадение с шаблоном.*Frank: 'Young Frank'.
Первое совпадение, найденное с помощью функции search()Вы можете использовать функцию search(), чтобы найти шаблон 'Frank' в любом месте строки-источника 'Young Frankenstein', не прибегая к использованию символа подстановки.*:
>>> m = re.search('Frank', source)
>>> if m:··# функция search возвращает объект
…·····print(m.group())
…
Frank
Ищем все совпадения с помощью функции findall()В предыдущих примерах мы искали только одно совпадение. Но что, если вы хотите узнать, сколько раз строка, содержащая один символ n, встречается в строке-источнике?
>>> m = re.findall('n', source)
>>> m···# findall returns a list
['n', 'n', 'n', 'n']
>>> print('Found', len(m), 'matches')
Found 4 matches
Как насчет строки 'n', за которой следует любой символ?
>>> m = re.findall('n.', source)
>>> m
['ng', 'nk', 'ns']
Обратите внимание на то, что в совпадения не была записана последняя строка 'n'. Нам нужно сказать, что символ после 'n' является опциональным, с помощью конструкции?:
>>> m = re.findall('n.?', source)
>>> m
['ng', 'nk', 'ns', 'n']
Разбиваем совпадения с помощью функции split()В следующем примере показано, как разбить строку на список с помощью шаблона, а не простой строки (как это делает метод split()):
>>> m = re.split('n', source)
>>> m····# функция split возвращает список
['You', 'g Fra', 'ke', 'stei', '']
Заменяем совпадения с помощью функции sub()Этот метод похож на метод replace(), но он ищет совпадения с шаблонами, а не простые строки:
>>> m = re.sub('n', '?', source)
>>> m···# sub returns a string
'You?g Fra?ke?stei?'
Шаблоны: специальные символыМногие описания регулярных выражений начинаются с деталей, касающихся того, как их определить. Я считаю, что это ошибка. Язык регулярных выражений не так уж мал сам по себе, слишком много деталей должно вместиться в вашу голову одновременно. Они используют так много знаков препинания, что это выглядит так, будто персонажи мультиков ругаются.
Теперь, когда вы знаете о нужных функциях (match(), search(), findall() и sub()), рассмотрим детали построения регулярных выражений. Создаваемые вами шаблоны подойдут к любой из этих функций.
Самые простые знаки вы уже видели.
• Совпадения с любыми неспециальными символами.
• Любой отдельный символ, кроме \n, — это символ..
• Любое число, включая 0, — это символ *.
• Опциональное значение (0 или 1) — это символ?.
Специальные символы показаны в табл. 7.3.
Таблица 7.3. Специальные символыШаблон Совпадения \d Цифровой символ \D Нецифровой символ \w Буквенный или цифровой символ или знак подчеркивания \W Любой символ, кроме буквенного или цифрового символа или знака подчеркивания \s Пробельный символ \S Непробельный символ \b Граница слова \B Не граница слова
Модуль Python 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', 'к', 'e']
Шаблоны: использование спецификаторовТеперь сделаем пиццу из знаков препинания, используя основные спецификаторы шаблонов для регулярных выражений, показанные в табл. 7.4.
В этой таблице expr и другие слова, выделенные курсивом, означают любое корректное регулярное выражение.
Таблица 7.4. Спецификаторы шаблоновШаблон Совпадения 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 (? next, если перед ним не находится prev
У вас могло зарябить в глазах при попытке прочесть эти примеры. Для начала определим строку-источник:
>>> source = '''I wish I may, I wish I might
… Have a dish of fish tonight.'''
Найдем во всем тексте строку 'wish':
>>> re.findall('wish', source)
['wish', 'wish']
Далее найдем во всем тексте строки 'wish' или 'fish':
>>> re.findall('wish|fish', source)
['wish', 'wish', 'fish']
Найдем строку 'wish' в начале текста:
>>> re.findall('^wish', source)
[]
Найдем строку 'I wish' в начале текста:
>>> re.findall('^I wish', source)
['I wish']
Найдем строку 'fish' в конце текста:
>>> re.findall('fish$', source)
[]
Наконец, найдем строку 'fish tonight.$' в конце текста:
>>> re.findall('fish tonight.$', source)
['fish tonight.']
Символы ^ и $ называются якорями: с помощью якоря ^ выполняется поиск в начале строки, а с помощью якоря $ — в конце. Сочетание.$ совпадает с любым символом в конце строки, включая точку, поэтому выражение сработало. Для обеспечения большей точности нужно создать управляющую последовательность, чтобы найти именно точку:
>>> 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)
[]
Почему этого не произошло? Как мы говорили в главе 2, Python использует специальные управляющие последовательности для строк. Например, \b для строки означает «возврат на шаг», но в мини-языке регулярных выражений эта последовательность означает начало слова. Избегайте случайного применения управляющих последовательностей, используя неформатированные строки Python, когда определяете строку регулярного выражения. Всегда размещайте символ r перед строкой шаблона регулярного выражения, и управляющие последовательности 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< name > expr), он совпадет с выражением expr, сохраняя совпадение в группе name:
>>> m = re.search(r'(?P. dish\b).*(?P\bfish)', source)
>>> m.group()
'a dish of fish'
>>> m.groups()
('a dish', 'fish')
>>> m.group('DISH')
'a dish'
>>> m.group('FISH')
'fish'
Бинарные данные