10. Регулярни изрази
3 април 2013
За какво ще си говорим днес
- Очевидно, регулярни изрази
- С какво ще ни улеснят живота — в и извън рамките на Python
- Особености в Python 3
- Плюс една дребна задачка (вече традиционна)
Проблематика
Работа с низове:
- Търсене на по-сложна последователност от символи в низ
- Заместване на такива последователности с нещо друго
- Проверка дали даден низ отговаря на определени условия
Примерни проблеми -- много
- Често срещано: извличане на информация от текстови и markup-документи (XML, HMTL, ...)
- Искаме да проверим дали даден низ съдържа валиден телефонен номер
Това означава:
- Трябва да съдържа само цифри
- Може да започва с код на населеното място: 02, 032 или 052
- След кода, дължината му може да е между 5 и 7 цифри
- Самият номер (след кода) не може да започва с 0, 1, 2, 3 или 4
- Затворете очи... Виждате ли редовете код вече?
Вариант за решение
def validate_phone_str(number):
if '02' == number[:2]:
return validate_phone_str(number[2:])
elif number[:3] in {'032', '052'}:
return validate_phone_str(number[3:])
if all([c.isdigit() for c in number]):
return 5 <= len(number) <= 7
return False
Втори вариант за решение
def validate_phone_re(number):
pattern = r'^(02|032|052)?[5-9]\d{4,6}$'
return bool(re.search(pattern, number))
- Cooler, eh?
- Ще се заемем да разучим този втори вариант
Преди това, обещаната задачка
Всичко е чудесно, но аз вече съм майстор на регулярните изрази.
За всички вас имаме следната задача:
Да се провери дали дадено число е просто чрез един ред пайтън-код и регулярен израз. Разрешени операции са
- Самото число, разбира се.
re.search
с подходящ шаблон.
- Употребата на низа '1'.
- Операторa
*
.
Решения по-късно
Понятия
- Основно: "шаблон" (pattern), още "регулярен израз"
- Специални (meta) символи
- Екраниране (escape-ване) на специалните символи
Регулярните изрази в контекста на Пайтън
- import re — модулът, реализиращ PCRE-функционалността
- Escape-ване на специални символи: чрез \
- За задаване на шаблоните обикновено се ползват raw-низовете
- Пример: r'\s+'
- Py3k unicode особености: unicode шаблон и низ, или 8-bit шаблон и низ — консистентност
Нашата помощна функция matcher
- Примерите ще демонстрираме чрез наша функция matcher().
- Не е част от стандартната библиотека на Python :)
- Ще ви покажем 4-те й реда код по-късно.
- Сигнатура: matcher(pattern, string).
Пример
>>> matcher('pat', 'Find a pattern.')
'Find a (pat)tern.'
>>> matcher('#', 'What ###?')
'What (#)##?'
Магия от level 1 -- Повторения (quantifiers)
Важат за непосредствено предхождащия ги символ/клас/група. Нека го означим с s.
- s* означава нула или повече повторения на s.
- s+ търси едно или повече повторения на s.
- s? съвпада с нула или едно повторение на s.
- s{m,n} означава между m и n повторения на s, където можем да пропуснем m или n. s{,n} има смисъл на нула до n повторения, а s{m,} — поне m повторения.`
Примери
matcher('o+', 'Goooooooogle') # 'G(oooooooo)gle'
matcher('[hH]o+', 'Hohohoho...') # '(Ho)hohoho...'
# Хм. Не искахме точно това. По-скоро:
matcher('([hH]o)+', 'Hohohoho...') # '(Hohohoho)...'
matcher('([hH]o){2,3}', 'Hohohoho...') # '(Hohoho)ho...'
Примери
По подразбиране - алчно търсене за съвпадение (greedy). Деактивира се с ? след квантора.
matcher('[hH]o+', 'Hoooooohohooo...') # '(Hoooooo)hohooo...'
matcher('[hH]o+?', 'Hoooooohohooo...') # '(Ho)ooooohohooo...'
Скоби и групиране
Символите ( и ) се използват за логическо групиране на части от шаблона с цел:
- Контролиране областта на влияние на дадена операция
- Възможност за референция към "ограденото" в скобите
- Задаване на по-специални (и не толкова често употребявани) конструкции
Повече за групите -- след малко.
Значения на специалните символи
- . съвпада с един произволен символ. По подразбиране символите за нов ред не се включват в тази група.
- ^ съвпада с началото на низ (или на ред, ако се работи в MULTILINE режим.)
- $ съвпада с края на низ (или на ред, ако се работи в MULTILINE режим.)
Значения на специалните символи
- | има смисъл на или, например:
matcher('day|nice', 'A nice dance-day.') # 'A (nice) dance-day.'
matcher('da(y|n)ce', 'A nice dance-day.') # 'A nice (dance)-day.'
NB! Единствено |
се прилага не над непосредствените му символи/класове, а на целия низ отляво/отдясно:
matcher('ab|c|e', 'abcdef') # '(ab)cdef'
matcher('am|c|e', 'abcdef') # 'ab(c)def'
matcher('a(m)|c|e', 'abcdef') # 'ab(c)def'
Магия от level 2 (DRY) -- Символни класове
- Набор от символи, заграден от [ и ], например [aeoui].
- Съвпадат с точно един от символите, описани в класа, например:
>>> matcher('[aeoui]', 'Google')
'G(o)ogle'
- Отрицание на клас — посредством ^ в началото на класа:
matcher('[^CBL][aeoui]', 'Cobol') # 'Co(bo)l'
>>> matcher('[0-9]{1,3}-[a-z]', 'Figure 42-b')
'Figure (42-b)'
>>> matcher('[^a-zA-Z-]', 'Figure-42-b')
'Figure-(4)2-b'
Предефинирани класове
- \d — една цифра; същото като [0-9].
- \D — един символ, който не е цифра; същото като [^0-9].
- \s — един whitespace символ — [\t\r\n\f\v].
- \S — един символ, който не е whitespace — [^\t\r\n\f\v].
- \w — една буква или цифра.
- \W — един символ, който не е буква или цифра.
- \b — нула символа, но граница на дума.
- И други.
Примери за употреба на класове
matcher(r'\d+', 'Phone number: 5551234')
# 'Phone number: (5551234)'
matcher(r'\w+', 'Phone number: 5551234')
# '(Phone) number: 5551234'
matcher(r'\s+', 'Phone number: 5551234')
# 'Phone( )number: 5551234'
Gandalf The Gray -- Групи
- Групите са частите от даден шаблон, оградени в ( и ).
- Към тях можем да се обръщаме и от самия шаблон чрез специалните класове \1 — първата група, \2 — втората и така нататък.
- Няколко примера:
matcher(r'(\w+).*\1', 'Matches str if str repeats one of its words.');
'M(atches str if str repeat)s one of its words.'
# Хм. Не точно. Нека опитаме пак:
matcher(r'(\b\w+\b).*\1', 'Matches str if str repeats one of its words.');
'Matches (str if str) repeats one of its words.'
Групи за напреднали (Gandalf The White)
- (?:...) — използване на скоби, без да се създава група.
- (?P...) — текстът, отговарящ на групата, може да бъде достъпван чрез име, вместо чрез номер.
- (?P=name) — търси съвпадение за текста, намерен по-рано от групата, кръстена name.
- (?#...) — коментар, игнорира се.
- (?=...) — съвпада, ако ... следва, но не го "консумира" (look-ahead).
- (?!...) — съвпада, ако ... не следва.
- (?(id/name)yes|no) — търси за шаблона 'yes', ако групата с номер/име съвпада, или с (опционалния) шаблон 'no' иначе.
- Още: help(re)
Методи на модула re
- re.search() — проверява дали даден низ съдържа текст, отговарящ на зададения шаблон
- re.match() — същото както горното, само че се търси за съвпадение в началото на низа
- re.findall() — връща като списък всички съвпадения на шаблона в дадения низ
- re.finditer() — същото като горното, но връща итератор
Методи на модула re (2)
- re.sub(pattern, repl, string, count=0) — заместване в низ, на база на шаблон
- re.split(pattern, string, maxsplit=0) — разделяне на низ на парчета, на база на шаблон
- re.escape(pattern) — escape-ва всички специални за регулярен израз символи
- Пример: re.escape('a(a)\s+') ще върне 'a\\(a\\)\\\\s\\+'
- Още: help(re)
MatchObject
- group() — връща частта от низа, отговаряща на шаблона (и още...)
- start() — връща началото на съвпадението в низа
- end() — връща края на съвпадението в низа
- span() — връща (start, end) под формата на tuple
Флагове
- re.I (re.IGNORECASE) — case-insensitive търсене.
- re.L (re.LOCALE) — кара \w, \W, \b, \B да зависят от текущия locale.
- re.M (re.MULTILINE) — кара "^" да съвпада както с начало на низ, така и с начало на ред, докато "$" ще съвпада с край на ред или края на низа.
- re.S (re.DOTALL) — "." ще съвпада с всеки символ, включително и нов ред.
- re.X (re.VERBOSE) — режим на игнориране на white-space и коментари (за по-дългички RE).
- re.А (re.ASCII) — кара \w, \W, \b, \B, \d, \D да отговарят на съответните ASCII-класове.
Кодът на matcher()
def matcher(regex, string):
match = re.search(regex, string)
if match is None: return string
start, end = match.span()
return string[:start]
+ '<<<' + string[start:end] + '>>>' +
string[end:]
На финалната права...
- Имате ли предложения за задачата от началото?
- 'prime' if not re.search(<някакъв шаблон>, '1' * <число>) else 'not prime'
- Шаблонът: r'^1?$|^(11+?)\1+$'. Въпроси?