Python/Регулярні вирази
Деякі люди стикаючись з проблемою думають "Я знаю, я використаю регулярні вирази". І в них з'являються дві проблеми.
Jamie Zawinski
Цей документ описує основи роботи з регулярними виразами за допомогою модуля re.
Вступ
[ред.]Модуль re з'явився в Python 1.5 і працював з регулярними виразами стилю Perl. Старіші весії Пайтона поставлялись з модулем regex, який реалізовував регулярні вирази в стилі Emacs. Модуль regex був вилучений з Python 2.5.
Регулярні вирази (часті скорочення: RE, regex) це спеціальна малесенька мова програмуання вбудована в Python за допомогою модуля re. За допомогою цієї мови можна описувати множини слів (рядків), які відповідають шаблону (регулярному виразу). Множинами можуть бути всі правильні адреси e-mail, команди TeX, чи будь-що що ви захочете. Потім можна отримувати відповіді на запитання "Чи відповідає цей рядок шаблону?", або "Чи зустрічається у цьому рядку послідовність символів що відповідає шаблону?". Також можна використовувати регулярні вирази щоб робити заміни в рядку, чи розділяти її на складові різними способами.
Регулярні вирази компілюються в послідовність байткодів, які потім виконуються підпрограмою написаною на C. Знання методів за допомогою яких виконуються співставляння рядків з регулярними виразами необхідне для їх оптимізації, але в цьому документі не розглядається.
Через те, що регулярні вирази - доволі мала та обмежена мова, тому не всі завдання з обробки рядків можна виконати з їх допомогою. Також є задачі, які робляться з регулярними виразами, але вони виходять занадто складними, і ефективніше написати код обробки рядка на Python. Хоча код на Python буде повільнішим за відповідний регулярний вираз, але буде набагато зрозумілішим.
Прості шаблони
[ред.]Почнемо вивчення з найпростіших регулярних виразів. І з найпростішої задачі, яку вони можуть виконувати - пошук відповідностей шаблону.
Для детального пояснення механізмів роботи регулярних виразів (детермінованих та недетермінованих скінченних автоматів) зверніться до будь-якого підручника з написання компіляторів, в розділ про лексичний аналіз.
Символи
[ред.]Більшість символів просто відповідають самі собі. Наприклад регулярний вираз test відповідає тільки рядку test. (Можна також увімкнути режим нечутливий до регістру, який зробить так що цей ревир буде відповідати рядкам Test, TEST і подібним. Детальніше про це пізніше.)
Є винятки з цього правила: деякі символи є спеціальними метасимволами, і не відповідають самі собі. Замість цього вони змінюють значення інших частин регулярного виразу. Більша частина цього документу присв'ячена саме метасимволам та їх функціям.
Ось повний список метасимволів, значення яких ми розглянемо далі:
. ^ $ * + ? { [ ] \ | ( )
Почнемо з символів [ та ]. Вони використовуються для задання множини (або класу) символів яка відповідає будь-якому з своїх елементів. Можна задавати цю множину простим перелічуванням, чи заданням діапазону, за допомогою символа "-" (мінус). Наприклад [abc] відповідатиме рядкам a, b або c. Цей регулярний вираз еквівалентний виразу [a-c], який задає цю ж множину як діапазон. Якщо потрібно описати всі маленькі літери латинського алфавіту - можна використати регулярний вираз [a-z].
Метасимволи не активні всередині квадратних дужок, і відповідають самі собі. Наприклад [ab$] відповідатиме рядкам a, b та $.
Можна також задавати множини доповненням до множини. Якщо всередині квадратних дужок перший символ - ^, то вони будуть відповідати всім символам що не перелічені всередині них. Наприклад [^5] буде відповідати всім символам крім 5. Якщо ^ зустрінеться за межами квадратних дужок, то буде відповідати сам собі.
Напевне найважливішим метасимволом є бекслеш \. Як і в літералах мови Python, бекслеш ставиться перед символами щоб позначити їх особливе значення, їх називають ескейп-символами. Також всі метасимволи після бекслешу перестають бути метасимволами. Наприклад \[ відповідає [ а вираз \\ рядку \.
Деякі ексейп-символи відповідають попередньо заданим множинам символів, таким як множини цифр, букв, всього що не є порожнім символом. Доступні такі попередньо задані множини:
Символ | Відповідає | Еквівалентний до |
---|---|---|
\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_] |
Ці символи теж можна включати в класи символів. Наприклад [\s,.] включатиме всі символи пропусків, та "," і ".".
І останнім метасимволом в цьому розділі буде ".". Вона відповідає всім символам крім символа нового рядка (\n), хоча є альтернативний режим (re.DOTALL) в якому вона буде відповідати всім символам. Використовується коли вам потрібно щоб рядок відповідав довільному символу.
Повторення
[ред.]Крім того що регулярні вирази можуть співставлятись різним множинам символів, вони можуть співставлятись послідовностям символів, які позначаються за допомогою повторень.
Перший метасимвол який ми розглянемо - це *. * не відповідає жодному символу, замість того він позначає той факт, що символ що йде перед ним може бути вставлений в рядок нуль чи більше разів, замість одного, як зазвичай. Наприклад, ca*t відповідатиме ct (нуль символів a), cat (1 a), caat (2 a), caaat (3 символи a) і так далі. Звичайно кількість повторень має обмеження пов'язані з максимальним значенням цілочисельної величини, яка їх рахує, але довжина рядка має обмеження пов'язані з пам'яттю, тому можна вважати, що a* відповідає довільній кількості символів a.
Повторення на зразок * є жадібними, при пошуку підрядка в рядку за шаблоном, вони старатимуться знайти підрядок найдовшої довжини.
У молодості перекладач цього документу робив помилки. А саме, хотів в HTML шукати виділення жирним шрифтом за допомогою виразу
<b>.*</b>
. І тоді в рядку на зразокa <b>b</b> c <b>d</b> d
цей регулярний вираз виділив не два жирні рядки, як можна було б сподіватись, а один -b</b> c <b>d
. Відповідно такий текст з'явився й на екрані жирним шрифтом, хоча там не мало з'являтись</b>
іc
не мало бути жирним.
Якщо стараючись загребсти якомога більше символів регулярний вираз перестане відповідати рядку, він просто повернеться назад на одне повторення. Проілюструємо на прикладі пошуку шаблона a[bcd]*b в рядку abcbd.
Крок | Співпало | Пояснення |
---|---|---|
1 | a | Відповідає a у РеВ |
2 | abcbd | Відповідає [bcd]* і захоплює якомога більше символів |
3 | Невдача | Старається знайти відповідник b, та поточна позиція знаходиться в кінці рядка, і більше символів нема. |
4 | abcb | Повертається назад, так що [bcd]* відповідає рядку коротшому на символ |
5 | Невдача | Знову пробує знайти відповідник b але зараз поточна позиція - символ d. |
6 | abc | Знову повертається назад на символ, і [bcd]* відповідає лиш bc. |
7 | abcb | Знову пробує шаблон b. Цього разу символ в поточній позиції - b, і знаходження відповідності завершується успіхом. |
Коли кінець регулярного виразу досягнуто він відповідає рядку abcb. Ми продемонстрували жадібність регулярного виразу, та його поступове повернення назад у пошуку співпадінь. Повернення назад завершиться у випадку нульової кількості входжень [bcd]*, і якщо навіть це завершиться невдачею, алгоритм зробить висновок що рядок взагалі не відповідає регулярному виразу.
Інший метасимвол що задає повторення це +, який відповідає одному чи більше входженню. Зауважте відмінність від * - + відповідає як мінімум одному входженню, в той час як * дозволяє взагалі не включати символи в рядок жодного разу. Використовуючи подібний приклад, ca+t буде відповідати cat, caat, але не буде відповідати ct.
Є ще кілька позначень повтору. Знак запитання, ?, відповідає одному чи нулю входжень, ним можна позначати щось необов'язкове. Наприклад Будь[ -]?ласка буде відповідати як Будь ласка та Будь-ласка, так і Будьласка.
Найскладнішим позначенням для повторення є {m,n}, де m та n - цілі десяткові числа. Вони означають, що має бути як мінімум m повторень, але не більше n. Наприклад a/{1,3}b відповідає a/b, a//b та a///b. Воно не відповідає ab, яке не має слешів, чи a////b яке має чотири.
Можна також не вказати m чи n. Якщо не вказати m то нижня межа прирівнюється нулю, а якщо n то верхня - нескінченність.
Зауважимо, що останнє позначення є універсальним, і може замінювати всі інші. Наприклад {0,} еквівалентно *, {1,} еквівалентно +, а {0,1} - ?. Тільки символи *, +, ? використовувати зручніше, бо вони коротші та легше читаються.
Використання регулярних виразів
[ред.]Ми вже глянули на деякі прості регулярні вирази. Як використовувати їх в Python? Модуль re надає інтерфейс до алгоритму регулярних виразів, дозволяючи компілювати регулярні вирази в об'єкти, і шукати співпадіння з їх допомогою.
Компіляція регулярних виразів
[ред.]Регулярні вирази компілюються у об'єкти шаблонів, які мають методи для різних операцій, таких як пошук відповідностей шаблону, чи виконання замін.
>>> import re >>> p = re.compile(’ab*’) >>> print p <_sre.SRE_Pattern object at 80b4150>
re.compile() також приймає необов'язковий аргумент flags, який використовується для задання деяких спеціальних налаштувань. Ми опишемо доступні налаштування пізніше, а зараз лише один приклад:
>>> p = re.compile(’ab*’, re.IGNORECASE)
Регулярний вираз передається в re.compile() як рядок. Регулярні вирази передаються як рядки, тому що вони не є частиною мови Python, і для них не створено спеціального синтаксису. Зберігання регулярних виразів в рядках робить мову Python простішою, та має один недолік, який ми розглянемо в наступному розділі.
Бекслешева чума
[ред.]Збірник задач з регулярних виразів
[ред.]Якщо ви використовували регулярні вирази в своєму житті - розповідайте які задачі перед вами стояли.