вторник, 9 июля 2013 г.

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

 

Регулярные выражения - это компактная форма записи представления о коллекции строк.

Регулярные выражения используются для:

- проверки наличия фрагмента текста в строке
- поиска подстрок
- поиска и замены
- разбиения строк по точкам совпадения

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

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

Простейшие регулярные выражения - это обычные литералы символов:

a, b, c ... 1, 2, 3 ...

Выражение:  11G
состоит из трех простейших регулярных выражений (литералов символов),
каждое из которых неявно определяет одно совпадение.

Выражение 11G будет совпадать с одним символом 1
за которым следует один символ 1
за которым следует один символ G

т.е. будет совпадать со строками:

11G
ORACLE11GR2


В модуле re содержится функция re.search(r, s, f)
которая возвращает объект совпадения,
если совпадение с регулярным выражением r обнаруживается в любом месте строки s
(с учетом флагов f, если они заданы)
В противном случае возвращается None



import re

str = 'ORACLE11GR2'
match = re.search(r'11G', str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>>found 11G

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

  Это очень удобно для однократного применения регулярного выражения,
но если одно и то же регулярное выражение требуется применить несколько раз,
можно избежать излишних затратна компиляцию при каждом использовании,
скомпилировав выражение всего один раз с помощью функции:
re.compile(r, f)


import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = re.search(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>>found 11G



А лучше писать так:

import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



Большинство символов могут использоваться как литералы.

Но некоторые из них имеют специальное значение в языке регулярных выражний:

 \ . ^ $ ? + * { } [ ] ( ) |

Чтобы их использовать как обычные символы (литералы) их нужно экранировать символом \

\\  \.  \$ и т.д.


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

\n - перевод строки
\t - символ табуляции

 Экранированные последовательности с шестнадцатеричными кодами символов:

\xHH
\uHHHH
\UHHHHHHHH


Символьный класс - это выражение состоящее из одного или нескольких символов заключенных в квадратные скобки.

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

Регулярное выражение r[ea]d
совпадает с red или radar
но не со словом read

Совпадение с единственной цифрой можно отыскать с помощью регулярного выражения:

[0123456789]

или в сокращенном виде:

[0-9]

Имеется возможность инвертировать значение символьного класса:

[^0-9]

соответствует любому не цифровому символу.


В символьном классе все специальные символы кроме \ , теряют свое специальное значение.
Но два символа  ^ и - в символьном классе приобретают новое значение:

^ - если первый символ в классе, то инверсия.
- - если не первый символ в классе, то диапазон.

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

\d   - цифра   [0-9]
\D   - не цифра [^0-9]
\s   - пробельный символ [ \t\n\r\f\v]
\S   - не пробельный символ [^ \t\n\r\f\v]
\w   - символ слова [a-zA-Z0-9_]
\W   - символ не слова [^a-zA-Z0-9_]
.    - любой символ за исключением \n  (если флаг re.DOTALL то \n включается)


Например регулярное выражение:

[\dA-Fa-f]
соответствует шестнадцатеричной цифре

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


Флаги модуля регулярных выражений:

reobj = re.compile("regex pattern", re.VERBOSE | re.IGNORECASE | re.DOTALL | re.MULTILINE)
reobj = re.compile(r'(?aimsx)regex pattern')


Oracle11GR2
[Oo][Rr][Aa][Cc][Ll][Ee]11[Gg][Rr]2

Игнорировать регистр в регулярном выражении можно так:
c = re.compile(r'11[Gg]')

Но лучше использовать флаг:
c = re.compile(r'11g', re.IGNORECASE)
c = re.compile(r'(?i)11g')


mport re
c = re.compile(r'11g', re.IGNORECASE)
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



import re
c = re.compile(r'(?i)11g')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



Флаги

re.A
re.ASCII

При наличии этого флага, проверки:
\b, \B, \s, \S, \w и \W
действуют так, как если бы они применялись к тексту,
содержащему только символы ASCII.
По умолчанию действие этих проверок основано на спецификации Юникода.


re.I
re.IGNORECASE

Поиск совпадений выполняется без учета регистра символов.


re.M
re.MULTILINE

При наличии этого флага, символ ^ соответствует началу текста и позиции сразу же после каждого перевода строки,
а символ $ соответствует позиции перед каждым символом перевода строки и концу текста.


re.S
re.DOTALL

При наличии этого флага символ . соответствует любому символу, включая символ перевода строки.


re.X
re.VERBOSE

Позволяет включать в регулярные выражения пробелы и комментарии.



Квантификаторы

Квантификаторы записываются в виде {m,n}
m - минимальное число совпадений с выражением
n - максимальное число совпадений с выражением

Например

e{1,1}e{1,1}     две e подряд ee

или тоже самое

e{2,2}

Слову feel  они соответствуют, а слову felt нет.

Сокращения.

Если в квантификаторе указать одно число

e{2}

то оно означает и минимум и максимум

т.е  e{2}  то же что и  e{2,2}

Если квантификатор не указан, то предполагается что он равен единице {1,1}  или {1}

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

чтобы найти совпадение со словами

travelled
traveled

можно использовать выражение

travel{1,2}ed
или
travell{0,1}ed

Квантификатор {0,1}  имеет сокращение ?

travell?ed

Есть еще два сокращения для квантификаторов:

+ - не менее одного  {1,n}
* - любое число      {0,n}

где n - означает максимально допустимое для квантификатора число, которое обычно не менее 32767

Квантификатор + очень удобен.
Например, для поиска соответствий целым числам  можно было бы использовать выражение \d+
т.к. оно совпадает с одной или боее цифрами.

В строке  4588.91 регулярное выражение \d+ найдет два совпадения: 4588 и 91

С помощью belevel+ed  можно отыскать слова с одним или более символами l

Например ошибки связанные с залипанием клавиш:

beleveled
belevelled
belevellled


Квантификатор * используется реже - потому что он может приводить к получению неожиданных результатов.

Например хотим найти все строки с комментариями  #

#*     - так не годится

Это выражение найдет и пустые строки (т.е. с нулевым совпадением)
Квантификаторы * и ? могут находить соответствие при нулевом числе совпадений.

Если вы используете квантификатор * или ? , то обязательно убедитесь, что хотя бы одно подвыражение
в регулярном выражениииспользует ненулевой квантификатор (т.е. отличный от * и ?)

Часто возможно заменить квантификатор * на ? и наоборот.

tasselled

Найти слово с одним или более символами l можно так:

tassell*ed
или
tassel+ed

а с двумя или более символами l можно так:

tasselll*ed
или
tassell+ed


Регулярное выражение \d+ будет соответствовать строке 136
Почему оно соответствует всем цифрам, а не только первой?

По умолчанию все квантификаторы являются жадными (или максимальными)
они стремятся соответствовать как можно большему числу символов.

Любой квантификатор можно сделать нежадным (или минимальным), добавив после него символ ?.

Знак вопроса имеет два разных значения:

когда он употребляется самостоятельно, он интерпретируется как сокращенная форма записи квантификатора {0,1}

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

Например выражение  \d+?

обнаружит соответствие в строке 136 в трех местах:
1, 3 и 6


Ранее была представлена функция

re.search(r,s,f) 

которая находила и возвращала объект совпадения с регулярным выражением r в любом месте строки s


Существует более ограниченная функция

re.match(r,s,f)

которая находит и возвращает объект совпадения с регулярным выражением r только если он находится в начале строки s


Но существует и более продвинутая функция

re.findall(r,s,f)

которая находит и возвращает все непересекающиеся совпадения с регулярным выражением r в строке s.
Если регулярное выражение содержит сохраняющие группы, для каждого совпадения возвращается кортеж
с сохраненными фрагментами.


import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = re.match(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')


>>did not find




import re

c = re.compile(r'11G')
str = '11GR2'
match = re.match(c, str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>found 11G




import re

c = re.compile(r'11G')
str = 'ORACLE11GR2'
match = c.search(str)
if match:
    print('found', match.group())
else:
    print('did not find')

>>>found 11G



С помощью функции  re.findall
можно увидеть разницу между жадным н не жадным квантификатором:


Жадный (максимальный):

import re

c = re.comьpile(r'\d+')
str = '0123456789'
tuples = re.findall(c, str)
print(tuples)

>>['0123456789']


Не жадный (минимальный):

import re

c = re.compile(r'\d+?')
str = '0123456789'
tuples = re.findall(c, str)
print(tuples)

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


Еще примеры с функцией  re.findall:

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

import re

c = re.compile(r'\bа[а-я]*')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

>>['адрес', 'адрес']


Тоже но теперь буква a (латинский алфавит):

import re

c = re.compile(r'\ba[a-z]*')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

>>['angor', 'andrey']


Найдем все электронные адреса в строке:

import re

c = re.compile(r'[\w\.-]+@[\w\.-]+')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)


>>['angor@oracle.com', 'andrey@sun.com']


Если регулярное выражение содержит сохраняющие группы, то их можно вывести в виде кортежа:

import re

c = re.compile(r'([\w\.-]+)@([\w\.-]+)')
str = 'электронный адрес angor@oracle.com, еще электронный адрес andrey@sun.com'
tuples = re.findall(c, str)
print(tuples)

for tuple in tuples:
    print(tuple[0])
    print(tuple[1])


>>[('angor', 'oracle.com'), ('andrey', 'sun.com')]

>>angor
>>oracle.com
>>andrey
>>sun.com


Квантификаторы регулярных выражений:

e?      Соответствует e{0,1}
        ноль или большее число вхождений выражения e
        по умолчанию жадный


e??     Соответствует e{0,1}?
        ноль или более число вхождений выражения e
        минимальный


e+      Соответствует e{1,}
        одно или более число вхождений выражения e
        по умолчанию жадный


e+?     Соответствует e{1,}?
        одно или более число вхождений выражения e
        минимальный


e*      Соответствует e{0,}
        ноль или более число вхождений выражения e
        по умолчанию жадный


e*?     Соответствует e{0,}?
        ноль или большее число вхождений выражения е
        минимальный


e{m}    Cоответствует точно m вхождениям выражения e


e{m,}   Соответствует по меньшей мере m вхождениям выражения e
        по умолчанию жадный


e{m,}?  Соответствует по меньшей мере m вхождениям выражения e
        минимальный


e{,n}   Соответствует не более чем n вхождениям выражения e
        по умолчанию жадный


e{,n}?  Соответствует не более чем n вхождениям выражения e
        минимальный
       
                       
e{m,n}  Соответствует не менее чем m и не более чем n вхождениям выражения e
        по умолчанию жадный
  

e{m,n}? Соответствует не менее чем m и не более чем n вхождениям выражения e
        минимальный



Примеры сокращенных форм:


\d+  соответствует одной или более цифрам
     стремится соответствовать как можно большему числу символов


                                                
\d+?  соответствует одной или более цифрам
      из последовательностей двух или более цифр будут найдены два или более вхождений по   одной цифре.


\d??  соответствует нулевому или большему числу цифр, но предпочтение будет отдано      нулевому числу совпадений.
      т.к. квантификатор минимальный
      он порождает ту же проблему что и *, и может находить соответствие с ничем,
      т.е. соответствовать любому тексту.



Найдем в некотором файле все теги изображений

Пример такого тега:

<IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">



import re

c = re.compile(r'(?i)<img.*>')
str = '<....  <IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IМG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>']


.*  означает ноль или более любых символов.
Жадный квантификатор захватил все символы > и остановился на последнем символе > в файле.
это не то что мы ожидали.


import re

c = re.compile(r'(?i)<img.*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']

или так:


import re

c = re.compile(r'(?i)<img[^>]*>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']

или скомбинируем два предыдущих решения вместе:

import re

c = re.compile(r'(?i)<img[^>]*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']


Во всех трех последних примерах на возвращается ожидаемая строка, казалось бы решение найдено.
Но если подумать то наши регулярные выражения найдут и такие строки:
<img>

хотя такой тег является недопустимым и вряд ли появится в реальном html-коде,
но мы все же должны в своем регулярном выражении ориентироваться на допустимые строки

Как известно, тег изображения должен иметь обязательный атрибут src.

Напишем более точное регулярное выражение:

<img\s+[^>]*?src="\w+[^>]*?>

- подстрока  <img

- один или более пробельных символов

- минимально ноль или более любых символов за исключением символа Ю
  (чтобы пропустить любые атрибуты, такие как alt)

- строка src="

- затем хотя бы один символ слова

- затем любой (включая нулевое число) символ за исключением >
  (чтобы пропустить любые другие атрибуты)

- закрывающая угловая скобка


import re

c = re.compile(r'(?i)<img\s+[^>]*?src="\w+[^>]*?>')
str = '<....  <IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">   ...>'
tuples = re.findall(c, str)
print(tuples)

>>['<IMG height=202 alt="Ленин в Смольном" src="images/lenin-v-smolnom.jpg" width=160 align="middle" title="Ленин в Смольном.">']


Регуряные выражения можно объединять через ИЛИ
тогда будет отыскиваться совпадение с любым альтернативным вариантом:

import re

c = re.compile(r'подпрограмма|алгоритм|подстрока')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', 'подстрока']



Если мы применим операцию группировки (она же операция сохранения совпадения)
то то что не попало в сохранение не будет выведено функцией re.findall:

import re

c = re.compile(r'(подпрограмма|алгоритм)|подстрока')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', '']


Круглые скобки преследуют две цели:

- сгруппирповать выражения
- сохранить текст, совпавший с выражением

Например:

подпрограмма|подстрока|алгоритм

можно записать и так:

под(программа|строка)|алгоритм

Но во втором выражении будет побочный эффект сохранением выражений

import re

c = re.compile(r'под(программа|строка)|алгоритм')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['программа', '', 'строка']

Функция возвратила кортеж для сохранаемых групп.


Если мы  хотим избавится от такого побочного эффекта группировки
(хотим чтобы работала только группировка, но совпадения не сохранались)
то это можно сделать так:

под(?:программа|строка)|алгоритм

поставив вслед за открывающейся круглой скобкой символы  ?:


import re

c = re.compile(r'под(?:программа|строка)|алгоритм')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>['подпрограмма', 'алгоритм', 'подстрока']


А что будет если  написать так:

(под(программа|строка)|алгоритм)

тут будут сохранены два фрагмента, если будет обнаружено совпадение с первым выражением:

подпрограмма или подстрока - первое сохранение
программа или строка       - второе сохранение

и одно сохранение, если будет обнаружено совпадение со вторым выражением:

алгоритм    - одно сохранение


import re

c = re.compile(r'(под(программа|строка)|алгоритм)')
str = 'подпрограмма алгоритм подстрока'
tuples = re.findall(c, str)
print(tuples)

>>[('подпрограмма', 'программа'), ('алгоритм', ''), ('подстрока', 'строка')]


Пусть некоторый файл состоит из строк вида:

ключ = значение

Ключ содержит только алфавитно-цифровые символы

[a-zA-Z0-9_]   подойдет т.е. ключ соответствует регулярному выражению \w


Значение может содержать любые символы (кроме перевода строки)

[^ \t\n\r\f\v]  любой непробельный символ \S не подойдет
т.к. наличие пробелов внутри значений допускается
поэтому выбираем  .+


Для каждой совпавшей строки выполним два сохранения:

(\w) = (.+)


Например имеется пара:

 предмет  =    квантовая механика

Как видим начальные и конечные пробелы присутствуют и у ключа и у значения
кроме того, значение и внутри себя содержит пробел.

Исключим из первого сохранения и начальные и конечные пробелы
а из второго пока только начальные

для поиска пробельных символов символ \s ([ \t\n\r\f\v]) не годится
т.к. ему также соответствует символ перевода строки
для наших целей вполне подойдет следующий символьный класс [ \t]
(пробел и табуляция)

[ \t]*(\w+)[ \t]*=[ \t]*(.+)


import re

c = re.compile(r'[ \t]*(\w+)[ \t]*=[ \t]*(.+)')
str = ' предмет  =   квантовая механика     '
tuples = re.findall(c, str)
print(tuples)

>>[('предмет', 'квантовая механика     ')]


Обратиться к захваченному тексту можно с помощью обратных ссылок
(ссылок на предшествующие группы)


\i

где i - порядковый номер сохранающей группы
номер i начинается с 1

Сохранение 0 выполняется автоматически, без применения круглых скобок
Оно хранит все соответствие, т.е. всю строку.

(\w+)\s+\1

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

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

Именуются сохранения так:

За открывающейся круглой скобкой указываем ?P<name>

например

(?P<word>\w+)

а затем обращаемся к ней так:
                           
(?P=word)

наш пример

(\w+)\s+\1

можно переписать так:

(?P<word>\w+)\s+(?P=word)


Проверки регулярных выражений.


^  -  соответствует началу текста.
      с флагои re.MULTILINE соответствует позиции сразу после каждого символа перевода строки.


$  -  соответствует концу текста.
      с флагои re.MULTILINE соответствует позиции сразу перед каждыь символом перевода строки.


\A  -  соответствует началу текста.
\Z  -  соответствует концу текста.


\b  -  соответствует границе слова (re.ASCII)
       внутри символьных классов обозначает символ забоя (backspace)

\B  -  соответствует границе не слова (re.ASCII)


(?=e)    -  совпадение обнаруживается, усли текст справа от позиции проверки соответствует выражению e,
            при этом изменения позиции поиска в тексте не происходит.
            Эта проверка называется опережающей проверкой, или позитивной опережающей проверкой.
            Например выражение:  \w+(?=\s+)
            найдет слово, за которым стоит один или несколько пробельных символов.

(?!e)    -  совпадение обнаруживается, если текст справа от позиции проверки не соответствует выражению e,
            при этом изменения позиции поиска в тексте не происходит.
            Эта проверка называется негативной опережающей проверкой.

(?<=e)   -  совпадение обнаруживается, если текст слева от позиции проверки соответствует выражению e,
            эта проверка называется позитивной ретроспективной проверкой.

(?<!e)   -  совпадение обнаруживается, если текст слева от позиции проверки не соответствует выражению e,
            эта проверка называется негативной ретроспективной проверкой.



Рассмотрим чем отличаются проверки ^,$ и \A, \Z


Так совпадение обнаруживается:

import re

pattern = '''\Aabc de fgh$'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc de fgh

А так не находит:

import re

pattern = '''\Aabc de fgh\Z'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")



Так тоже совпадение обнаруживается:

import re

pattern = '''abc de fgh'''
c = re.compile(pattern)
line ='abc de fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc de fgh


Так совпадение обнаруживается:

import re

pattern = '''abc\nde fgh'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh


Так совпадение обнаруживается:

import re

pattern = '''abc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

И так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

И так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh\Z'''
c = re.compile(pattern)
line ='abc\nde fgh'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh


Так совпадение обнаруживается:

import re

pattern = '''\Aabc\nde fgh$'''
c = re.compile(pattern)
line ='abc\nde fgh\n'
match = c.search(line)
if match:
    print(line, end="")

abc
de fgh

А так нет:

import re

pattern = '''\Aabc\nde fgh\Z'''
c = re.compile(pattern)
line ='abc\nde fgh\n'
match = c.search(line)
if match:
    print(line, end="")



В итоге получается что паттерн "строка\Z"
совпадает со строкой "строка"
и не совпадает со строкой "строка\n"
Паттерн "строка$"
совпадает и строкой "строка"
и со строкой "строка\n"


Паттерн "строка"  также
совпадает и строкой "строка"
и со строкой "строка\n"


Паттерн "строка"
совпадает и строкой "строка"
и со строкой "\nстрока\n"
но ни один из паттернов :

"^строка"
"\Aстрока"

не совпадет со строкой:

"\nстрока"


Проверки \b и \B


Регулярное выражение jet будет находить в тексте:

the jet and jetski are noisy

несколько соответствий.

Проверка \b - граница слова поможет избежать лишних нахождений.

Выражение \bjet\b  будет находить только одно соответствие.


import re

c = re.compile(r'(jet)')
str = 'the jet and jetski are noisy'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['jet', 'jet']


import re

c = re.compile(r'(\bjet\b)')
str = 'the jet and jetski are noisy'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['jet']



Выражение  \bg

найдет соответствие в тексте:

good 

и тексте:

a good  

но не найдет соответствия в тексте:

together

Выражение \Bg

не найдет соответствия в тексте:

good

и тексте:

a good  

но найдет соответствие в тексте:

together


import re

c = re.compile(r'(\bg)')
str = 'together'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
[]


import re

c = re.compile(r'(\Bg)')
str = 'together'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test1.py
['g']



Выражение:

aircraft|airplane|jet

можно записать так:

\baircraft\b|\bairplane\b|\bjetb\

или более просто:

\b(?:aircraft|airplane|jet)\b

т.е.
граница слова, несохраняющее выражение, граница слова

так как, выражения:

aircraft|airplane|jet
\baircraft\b|\bairplane\b|\bjetb\

являются несохраняющими
то и мы должны использовать несохраняющее выражение:

\b(?:aircraft|airplane|jet)\b


Опережающие проверки.

Пусть имеется текст:

Helen Patricia Sharman
Jim Sharman
Sharman Joshi
Helen Kelly

нам необходимо отыскать текст Helen Patricia, но только если он относится к имени Sharman.

Простейший способ такой:

\b(Helen\s+Patricia)\s+Sharman\b

Этому выражению будет соответствовать текст:
Helen Patricia, только если он предшествует границе слова,
за которым следуют пробельные символы и слово Sharman и далее следует граница слова.

Однако того же самого можно добиться с помощью опережающей проверки:

\b(Helen\s+Patricia)(?=\s+Sharman\b)


Чтобы сохранить определенное имя в самых разных его разновидностях:

Helen
Helen P.
Helen Patricia

можно задать такое регулярное выражение:

\b(Helen(?:\s+(?:P\.|Patricia))?)\s+(?=Sharman\b)

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

(e)
(?P<name>e)

других форм сохраняющих группировок не существует.


Ретроспективные проверки.

Выражения, которые могут использоваться в ретроспективных проверках,
должны иметь фиксированную длину.
Т.е. в них не могут использоваться квантификаторы ?, + и *.
А интервальные квантификаторы должны задавать фиксированный размер: например {3}.


Регулярное выражение [ \t]*
найдет ноль или более символов пробелов и табуляций, т.к. квантификатор жадный,
то пока он не найдет их все (идущие друг за другом) он не остановится.

Есть строка любых символов  (.+)

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

[ \t]*(.+)

Вспомнив, что  . (точка) - это любой символ за исключением перевода строки, перепишем это выражение так:

[ \t]*([^\n]+)

И еще вспомнив, что при наличии флага re.MULTILINE символ ^ соответствует началу текста и
позиции сразу же после каждого символа перевода строки, перепишем это выражение так:

^[ \t]*([^\n]+)


Как же удалить конечные пробелы ?

Например пусть имеется строка:

aaabb  cde    fff    z          \t         \n

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

([^\n]+)

Позиция проверки доходит до символа \n
т.к. все символы, кроме \n,  удовлетворяют условию данного регулярного выражения.

далее используем негативную обратную проверку:  (?<![ \t])

([^\n]+)(?<![ \t])

негативная обратная проверка для совпадения требует, чтобы символы,
предшествующие позиции проверки, не были пробелами или символами табуляции.

Вследствии этого последний символ "значения" (в нашем случае это - z) сохраняется в сохраняющей группе,
а завершающие пробелы и символы табуляции - нет.


import re

c = re.compile(r'([^\n]+)(?<![ \t])')
str = 'aaabb  cde    fff    z          \t         \n'
tuples = re.findall(c, str)
print(tuples)

C:\projects>python test.py
['aaabb  cde    fff    z']



Вернемся к нашему регулярному выражению,
которое отыскивает пары ключ-значение:

^(\w+)=([^\n]+)

Чтобы гарантировать, что каждая пара ключ=значение будет извлекаться из единственной строки
и не будет происходить объединения нескольких строк необходимо установить флаг re.MULTILINE

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

^[ \t]*(?P<key>\w+)[ \t]*=[ \t]*(?P<value>[^\n]+)(?<![ \t])

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

(?# текст комментария)
но на практике такие комментарии могут еще больше осложнить понимание.

Лучшее решение заключается в использовании флага re.VERBOSE

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

- когда необходимо описать совпадение с пробельным символом, следует использовать
либо символ \s либо символьный класс, такой как [].

Пример нашего регулярного выражения:

^[ \t]*                 # начало строки и необязательные начальные пробелы
(?P<key>\w+)            # текст ключа
[ \t]*=[ \t]*           # знак равенства с возможными пробелами, окружающими его
(?P<value>[^\n]+)       # текст значения
(?<![ \t])              # негативная ретроспективная проверка для исключения завершающих пробелов.


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

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

c = re.compile(r"""
               ^[ \t]*                 # начало строки и необязательные начальные пробелы
               (?P<key>\w+)            # текст ключа
               [ \t]*=[ \t]*           # знак равенства с возможными пробелами, окружающими его
               (?P<value>[^\n]+)       # текст значения
               (?<![ \t])              # негативная ретроспективная проверка для исключения завершающих пробелов.
               """, re.MULTILINE|re.VERBOSE)




Ранее мы видели как можно получить доступ к сохраненному тексту внутри регулярного выражения:

- по номеру группы
- по имени группы

(.....)(.....)(.....)\1
   1      2      3


(?P<name>)(.....)(.....)(?P=name)
      1       2      3


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


(.....)(.....)(.....)(?(2) expr1)
   1      2      3

если было совпадение во втором сохранении то expr1



(.....)(.....)(.....)(?(2) expr1|expr2)
   1      2      3

если было совпадение во втором сохранении то expr1, иначе expr2



Рассмотрим несколько вариантов записи атрибута src

значения атрибута могут заключаться в апострофы, кавычки или без кавычек.

src="girl.png"
src='girl.png'
src=girl.png


Как найти такой атрибут?

src=(["'])([^"'>]+)\1
        1      2

Сохранение 1, сохраняет найденное совпадение содержащее либо кавычки либо апострофы.

Сохранение 2, сохраняет найденное совпадение максимальной длины содержащее по крайней мере один символ,
который не является кавычкой, апострофом или ">".

Благодаря обратной ссылке \1, соответствие будет обнаружено, только если кавычки,
обрамляющие имя файла - одного типа.

Какой тип кавычек вернет первое сохранение, таким должно и закрываться.

Но это выражение не позволяет отыскивать имена файлов без кавычек.

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

src=(["'])?([^"'>]+)(?(1)\1)

Окончательный вид регулярного выражения

<img\s+                                       # начало текста
[^>]*?                                        # любые атрибуты предшествующие src
src=                                                # начало атрибута src
(?P<quote>["'])?            # необязательные открывающие кавычки
(?P<image>[^"'>]+)     # имя файла изображения
(?(quote)(?P=quote))                       # закрывающая кавычка, если была открывающая
[^>]*?                                         # любые атрибуты, следующие за src
>                                                 # конец тега


Конечно существует более простая, но менее очевидная альтернатива:

src=(["']?)([^"'>]+)\1

Если имеется символ открывающей кавычки, он сохраняется в группе 1

Далее идут символы, следующие за открывающей кавычкой.

Если открывающая кавычка отсутствовала, то по прежнему будет считаться,
что для группы 1 было найдено совпадение - совпадение с пустой строкой,
т.к. кавычка считается необязательной.
(ее квантификатор означает ноль или более совпадений)

В этом случае обратной ссылке также будет соответствовать пустая строка.








Комментариев нет:

Отправить комментарий