Эта серия примеров основана на реальных проблемах, которые появились в моей работе несколько лет назад, когда мне пришлось обработать и стандартизировать адреса улиц, экспортированных из устаревшей системы до того, как произвести импорт в новую систему. (Обратите внимание: это не придуманный пример, им всё ещё можно пользоваться). Этот пример показывает как я подошёл к проблеме:
s = ‘100 NORTH MAIN ROAD’
s.replace(‘ROAD’, ‘RD.’) ?
‘100 NORTH MAIN RD.’
s = ‘100 NORTH BROAD ROAD’
s.replace(‘ROAD’, ‘RD.’) ?
‘100 NORTH BRD. RD.’
UNIQae610d7ca506639d-nowiki-00000003-QINU ?
‘100 NORTH BROAD RD.’
import re ?
re.sub(‘ROAD$’, ‘RD.’, s) ?
‘100 NORTH BROAD RD.’
- ? Моя задача стандартизировать адрес улицы, например ‘ROAD’ всегда выражается сокращением ‘RD.’. На первый взгляд мне показалось, что это достаточно просто, и я могу использовать метод replace(). В конце концов, все данные уже в верхнем регистре и несовпадение регистра не составит проблемы. Строка поиска ‘ROAD’ являлась константой и обманчиво простой пример s.replace() вероятно работает.
- ? Жизнь же, напротив, полна противоречивых примеров, и я быстро обнаружил один из них. Проблема заключалась в том что ‘ROAD’ появилась в адресе дважды, один раз как ‘ROAD’, а во второй как часть названия улицы ‘BROAD’. Метод replace() обнаруживал 2 вхождения и слепо заменял оба, разрушая таким образом правильный адрес.
- ? Чтобы решить эту проблему вхождения более одной подстроки ‘ROAD’, вам необходимо прибегнуть к следующему: искать и заменять ‘ROAD’ в последних четырёх символах адреса (s[-4:]), оставляя строку отдельно (s[:-4]). Как вы могли заметить, это уже становится громоздким. К примеру, шаблон зависит от длины заменяемой строки. (Если вы заменяли ‘STREET’ на ‘ST.’, вам придется использовать s[:-6] и s[-6:].replace(…).) Не хотели бы вы вернуться к этому коду через полгода для отладки? Я не хотел бы.
- ? Пришло время перейти к регулярным выражениям. В Python все функции, связанные с регулярными выражениями содержится в модуле re.
- ? Взглянем на первый параметр: ‘ROAD$’. Это простое регулярное выражение которое находит ‘ROAD’ только в конце строки. Знак $ означает «конец строки». (Также существует символ ^, означающий «начало строки».) Используя функцию re.sub() вы ищете в строке s регулярное выражение ‘ROAD$’ и заменяете на ‘RD.’. Оно совпадает с ‘ROAD’ в конце строки s, но не совпадает с ‘ROAD’, являющимся частью названия ‘BROAD’, так как оно находится в середине строки s.
Продолжая историю про обработку адресов, я скоро обнаружил, что предыдущий пример совпадения ‘ROAD’ на конце адреса был недостаточно хорош, так как не все адреса включали в себя определение улицы. Некоторые адреса просто оканчивались названием улицы. Я избегал этого в большинстве случаев, но если название улицы было ‘BROAD’, тогда регулярное выражение совпадало с ‘ROAD’ на конце строки ‘BROAD’, чего я совершенно не хотел.
s = ‘100 BROAD’
re.sub(‘ROAD$’, ‘RD.’, s)
‘100 BRD.’
re.sub(‘\\bROAD$’, ‘RD.’, s) ?
‘100 BROAD’
re.sub(r’\bROAD$’, ‘RD.’, s) ?
‘100 BROAD’
s = ‘100 BROAD ROAD APT. 3’
re.sub(r’\bROAD$’, ‘RD.’, s) ?
‘100 BROAD ROAD APT. 3’
re.sub(r’\bROAD\b’, ‘RD.’, s) ?
‘100 BROAD RD. APT 3’
- ? В действительности я хотел совпадения с ‘ROAD’ когда оно на конце строки и является самостоятельным словом (а не частью большего). Чтобы описать это в регулярном выражении необходимо использовать ‘\b’, что означает «слово должно оказаться прямо тут.» В Python это сложно, так как ‘\’ знак в строке должен быть экранирован. Иногда это называют как «бедствие бэкслэша» и это одна из причин почему регулярные выражения проще в Perl чем в Python. Однако недостаток Perl в том что регулярные выражения смешиваются с другим синтаксисом, если у вас ошибка, достаточно сложно определить где она, в синтаксисе или в регулярном выражении.
- ? Чтобы обойти проблему «бедствие бэкслэша» вы можете использовать то, что называется неформатированная строка (raw string), путём применения префикса строки при помощи символа ‘r’. Это скажет Python-у что ничего в этой строке не должно быть экранировано; ‘\t’ это табулятор, но r’\t’ это символ бэкслэша ‘\’ , а следом за ним буква ‘t’. Я рекомендую всегда использовать неформатированную строку, когда вы имеете дело с регулярными выражениями; с другой стороны всё становится достаточно путанным (несмотря на то что наше регулярное выражения уже достаточно запутано).
- ? *вздох* К неудаче я скоро обнаружил больше причин противоречащих моей логике. В этом случае адрес улицы содержал в себе цельное отдельное слово ‘ROAD’ и оно не было на конце строки, так как адрес содержал номер квартиры после определения улицы. Так как слово ‘ROAD’ не находится в конце строки, регулярное выражение re.sub() его пропускало и мы получали на выходе ту же строку что и на входе, а это то чего вы не хотите.
- ? Чтобы решить эту проблему я удалил символ ‘$’ и добавил ещё один ‘\b’. Теперь регулярное выражение совпадало с ‘ROAD’ если оно являлось цельным словом в любой части строки, на конце, в середине и в начале.
?
Статьи к прочтению:
Как провести общее собрание собственников в многоквартирном доме?
Похожие статьи:
-
Скорее всего вы видели римские цифры, даже если вы в них не разбираетесь. Вы могли видеть их на копирайтах старых фильмов и ТВ-шоу («Copyright MCMXLVI»…
-
Учебный пример: обработка телефонных номеров
\d совпадает с любыми цифрами (0–9). \D совпадает со всем кроме цифр До сих пор вы были сконцентрированы на полных патернах. Совпадает патерн или не…