Саммерфилд - Программирование на Python 3 (1077331), страница 123
Текст из файла (страница 123)
Например, если представить, что имеется список имен в формате Имя ВтороеИмя1 ... ВтороеИмяИ Фамилия, в котором может указываться произвольное число вторых имен (или даже ни одного), и нам необходимо создать новый список, каждый элемент 546 Глава 12. Регулярные выражения которого имеет формат: Фамилия Имя ВтороеИмл1 ... ВтороеИмяХ, то этого легко можно было бы добиться с помощью регулярного выражения: па>г завез = () Гог паве (о ааааа: пава = ге.аоЬ(г (<>г+(о:'<а+'<и+) )~и+(~~в+)", г"'~2, >,1", папе) пав павез.арреоо(пава) В первой части (~я+(?: ~а+~я+)*) регулярного выражения первому подвыражению ~в+ соответствует имя, а подвыражению (?: ~э+~в+) * — ноль или более вторых имен. Подвыражению второго имени соответствуют ноль или более вхождений комбинации из пробельного символа и следующего за ннм слова.
Второй части ~а+( 1я+) регулярного выражения соответствует пробельный символ, за которым следует имя (и вторые имена) и фамилия. Если такое регулярное выражение кажется вам бессмысленным нагромождением, можно попробовать использовать именованные сохраняющие группы, чтобы повысить удобочитаемость и простоту сопровождения: паве = ге.аоо(г"(>Р<Гогеоавеа>'<>н(?:'<а.>~и+)*)" г"~а+(?Р<аогоава>'<гг>)", г"'<о<аз<лаве>, <,д<Гогаоавеа>", раве) Получить доступ к сохраненному тексту в функциях или в методах зьЬ() и эоЬп() можно с помощью синтаксической конструкции ~1 или '1д<? <г>, где т — это номер сохраняющей группы, а з о — это имя или номер сохраняющей группы, то есть \1 — это то же самое, что и ~д<1>, а в данном примере это то же самое, что и ~д<гогелааез>.
Этот же синтаксис можно использовать в строках, передаваемых методу ехрале( ) объекта совпадения. Почему первая часть регулярного выражения не захватывает имя, вторые имена и отчество целиком г В конце концов, в ней используется максимальный квантификатор. В действительности именно это и происходит, но тогда вторая часть регулярного выражения терпит неудачу. Часть, соответствующая вторым именам, допускает ноль или более совпадений, а часть, соответствующая фамилии, должна иметь точно одно совпадение, но из-за своей жадности выражение, соответствующее вторым именам, захватило все.
Когда обнаруживается неудача, механизм регулярных выражений выполняет шаг назад, освобождая последнее <второе имая и обеспечивая тем самым совпадение для части выражения, которая соответствует фамилии. Несмотря на то, что подвыражения с максимальными квантификаторами стремятся отыскать соответствие максимально возможной длины, тем не менее онн останавливаются, достигнув позиции, когда дальнейшее увеличение длины совпадения может привести к неудаче. Модуль для работы с регулярными выражениями 547 Например, если представить, что текст содержит имя «дешев ЪЧ.
Ьоетчеп», регулярное выражение сначала найдет совпадение всего имени с первой частью, то есть завез.йя«.1оейеп, Это удовлетворяет первую часть регулярного выражения, но оставляет ни с чем вторую часть, соответствующую фамилии, а поскольку совпадение с этой частью является обязательным (она имеет неявный квантификатор (1] ), то регулярное выражение в целом потерпит неудачу. Так как часть выражения, соответствующая вторым именам, снабжена квантификатором *, для нее может иметься ноль или более совпадений (в текущий момент для нее обнаружено два совпадения: «ЪУ. » и «Ьоеигеп»), поэтому механизм регулярных выражений заставляет эту часть выражения отдать последнее совпвдение, чтобы все регулярное выражение не потерпело неудачу.
Такой шаг назад возвращает последнее совпадение с ~в+~и (то есть текст «Ьоетчеп»); тогда совпадение приобретает вид Завез»И! Соеиеп, которое удовлетворяет все регулярное выражение целиком, и в двух группах сохраняется корректный текст. При использовании оператора выбора ( ~ ) с двумя или более сохраняющими альтернативами нет никакой возможности определить, какая из альтернатив обнаружила совпадение, поэтому нельзя определить, из какой группы следует извлекать сохраненный текст. Конечно, можно выполнить итерации по всем группам и отыскать непустую, но очень часто в подобной ситуации атрибут 1аэт1поех объекта совпадения может содержать номер нужной группы.
Чтобы проиллюстрировать это и получить дополнительные практические навыки работы с регулярными выражениями, рассмотрим последний пример. Предположим, что нам требуется определить, какая кодировка используется в файлах НТМЬ, ХМ1 или РуФ)топ. Мы могли бы открыть файл в двоичном режиме и прочитать, например, первые 1 000 байтов в объект типа Ьутез. После этого мы могли бы закрыть файл, определить кодировку по фрагменту в объекте Ьутез и вновь открыть файл в текстовом режиме с использованием обнаруженной кодировки или кодировки по умолчанию (\1ТУ-8).
Механизм регулярных выражений ожидает, что регулярные выражения будут поставляться ему в виде строк, а текст, к которому применяется регулярное выражение, может быть представлен объектом типа зтг, Ьутез или Ьутеаггау, причем, когда используются объекты типа Ьутез или Ьутеа ггау, все функции и методы вместо строк возвращают результат типа Ьутез, при этом неявно устанавливается флаг ге. АЯС11. В файлах НТМЬ кодировка обычно указывается в тете <веса> (если он вообще присутствует), например, <вета Ьттр-ецч1ч='Соптепт-Туре' соптепт='гехт/Ьтв1; сЬзгзет=180-8859-1'/>.
В файлах ХМЬ по умолчанию подразумевается кодировка ПТУ-8, но имеется возможность явно определить другую кодировку, например, <»хв1 чегзтопы'1.0" епсод(п9=" 8лттт 018"?>. В Ру$)топ 3 для файлов с исходным программным кодом также по умолчанию подразумевается кодировка ПТР-8, но здесь так- 548 Глава 12. Регулярные выражения же имеется возможность явно определить другую кодировку, включив вфайлтакую строку:1а!!п1илия - ° - со6!ЬО: 1а!!п1 -*-сразувследза строкой «в]1еЬапя». Ниже показано, как отыскать кодировку в предположении, что переменная Ь! па гу ссылается на объект Ьу!ез, содержащий первые 1 000 байтов из файла НТМ1, ХМ] или Ру!)юп: ва!сь = ге.зеагсп(г"""(?«и [ив]) Я! (?:(»:еп)?ссп!пз[сяагзе!) 82 (»;=([ ])»([-1»]+)(«(1Р 1) аЗ [: 15*([-',и]«))""".ЕПСО6Е("Сг?8"), шпагу, ге.
!ОЗОВЕСАЯЕ[ге.НЕВВОЗЕ) епсоп!пз = загса.згоср(загса. 1азг!ппех) !г загса е1ве ьчпг8" Чтобы выполнить поиск по объекту типа Ьугез, необходимо указать текст регулярного выражения также в виде объекта Ьу!ев. В данном случае мы предпочли воспользоваться удобствами, которые дают «сырые» строки, поэтому использовали в первом аргументе функции ге.
ВеагсЬ() такую строку, попутно преобразовав ее в объект типа Ьугев. Первая часть самого регулярного выражения является ретроспективной проверкой, которая говорит, что совпадению не может предшествовать дефис или символ «слова». Второй части выражения соответствуют слова «епсо61пя», «со<[1пя» и «с)!агаев», и ее можно было бы записать как (?:епсо6!ЬО)со6!ЬО)сяагзе?). Третья часть выражения была записана в двух строках, чтобы подчеркнуть, что она состоит из двух вариантов: =(["'])?([-'1и]«) (?(1)11) и: 1з*([-'1в]+), из которых только один может совпадать с текстом. Первой части соответствует знак равенства, за которым следует один или более символов «слова» или дефисов (которые, возможно, заключены в пару однотипных кавычек; применяется прием определения совпадения по условию), а второй части соответствует символ двоеточия, за которым следует необязательный пробельный символ и один или более символов «слова» или дефисов.
(Не забывайте, что внутри символьных классов символ дефиса интерпретируется просто как символ, если он стоит первым; в противном случае он обозначает диапазон символов, например, [0-9].) Мы использовали флаг ге,1ОйОЯЕСАВЕ, чтобы избежать необходимости записывать выражение (?: (?: [Ее][йп])? [Сс][ОЬ][06] [1!][йп][ОО] [ [Сс][НЬ] [Аа][нг][8зПЕе][ТГ]), и флаг ге, НЕЯВОЗЕ, чтобы регулярное выражение можно было оформить более удобочитаемым способом и снабдить его комментариями (в данном случае это всего лишь числа, отмечающие части регулярного выражения, на которые потом делаются ссылки в тексте). В заключение Здесь имеется три сохраняющих группы, причем все они находятся в третьей части: первая группа (["'3)г сохраняет необязательную открывающую кавычку, вторая группа ([-~в)+) сохраняет название кодировки, следующее за знаком =, и третья группа ([-~в]+) (в следующей строке) сохраняет название кодировки, следующее за двоеточием.
Нас интересует только название кодировки, поэтому нам требуется извлечь только фрагмент, сохраненный второй или третьей группой, причем только из той, которая участвовала в совпадении, так как они являются альтернативными. Атрибут 1азшлоех содержит индекс последней совпавшей группы (либо 2, либо 3 в этом примере, если совпадение с регулярным выражением будет найдено), поэтому мы извлекаем содержимое совпавшей группы или используем кодировку по умолчанию, если совпадение не было найдено. К настоящему моменту мы познакомились со всеми наиболее часто используемыми функциональными возможностями модуля ге в действии; тем не менее в заключение этого раздела упомянем еще одну, последнюю функцию, Функция эр1зг() (или метод эр1!1() объекта регулярного выражения) может разбивать строки с применением регулярных выражений.
На практике часто бывает необходимо разбить текст по пробельным символам, чтобы получить список слов (или, более точно, список строк, каждая из которых соответствует регулярному выражению ~В+). Регулярные выражения обладают широчайшими возможностями и, изучив их, легко заметить, что любые задачи, связанные с обработкой текста, могут быть решены с привлечением регулярных выражений. Но иногда строковые методы дают более высокую производительность и в большей степени соответствуют решаемой задаче.