Регулярни изрази
Проблем
Десет реда за една дата
Формулярът за резервация иска дата във вида дд.мм.гггг. Проверяваш го символ по символ:
def is_date(s): if len(s) != 10: return False for i in range(len(s)): c = s[i] if i == 2 or i == 5: if c != ".": return False elif c < "0" or c > "9": return False return True
print(is_date("05.03.2026"))print(is_date("5.3.2026"))print(is_date("05/03/2026"))TrueFalseFalseДесет реда, а функцията проверява само формата: две цифри, точка, две цифри, точка, четири цифри. Ако искаш да приемаш и / вместо точка, или ден с една цифра, ще добавиш още толкова. Номер на телефон, имейл или регистрационен номер ще ти струват по още една такава функция.
Идеята
Шаблон вместо код
Описваш как изглежда текстът, а Python проверява:
import re
def is_date(s): return re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", s) is not None
print(is_date("05.03.2026"))print(is_date("5.3.2026"))print(is_date("05/03/2026"))TrueFalseFalseШаблонът се чете отляво надясно: \d е цифра, {2} значи „два пъти“, \. е точка. Такъв шаблон се казва регулярен израз (regular expression, regex).
Регулярните изрази са в модула re, затова файлът започва с import re. re.fullmatch(шаблон, текст) проверява дали целият текст отговаря на шаблона, от първия до последния символ. Ако да, връща съвпадението, ако не, връща None. Затова is not None дава True или False.
Защо пред кавичките има r? Обратната наклонена черта е специална и за обикновения низ в Python. С r низът е суров (raw) и \ в него е просто \. Ще го видим подробно след малко. Засега пиши всеки шаблон с r.
Виж го
Как шаблонът търси
re.sub не проверява целия текст, а търси парчетата, които отговарят на шаблона, и ги заменя. Ето как минава по реда:
import re
line = "120 € на 05.03.2026, падеж 15.04.2026"masked = re.sub(r"\d{2}\.\d{2}\.\d{4}", "[дата]", line)print(masked)Стъпка по стъпка, с думи
- Редът е поредица от знаци, всеки със своя позиция.
- От позиция 0: „1“ и „2“ са цифри, но после идва „0“ вместо точка. Опитът пада.
- Нов опит от всеки следващ знак: „20 “, „0 “, „ €“… все неуспехи.
- От позиция 9 всичките десет части пасват: първото съвпадение.
- Търсенето продължава след съвпадението, от запетаята, и намира второто.
- re.sub заменя двете съвпадения с „[дата]“.
Шаблонът се пробва от всяка позиция поред. Щом някоя част не съвпадне, опитът пада и започва нов от следващия символ. Затова 120 не мина: две цифри, а после 0 вместо точка. Когато намери съвпадение, търсенето продължава след него.
Идеята
Класове от символи
В квадратни скоби изброяваш кои символи са позволени на едно място. Регистрационен номер на кола е една или две букви, четири цифри и две букви:
import re
plates = ["СА1234ВХ", "В7788КК", "СА12345ВХ", "са1234вх"]for plate in plates: print(plate, re.fullmatch(r"[А-Я]{1,2}\d{4}[А-Я]{2}", plate) is not None)СА1234ВХ TrueВ7788КК TrueСА12345ВХ Falseса1234вх False| Шаблон | Съвпада с един символ |
|---|---|
[АВЕ] |
А, В или Е |
[0-9], [А-Я] |
от интервала: цифра, главна буква на кирилица |
[^0-9] |
всичко освен цифра (^ в началото на скобите) |
\d |
цифра, същото като [0-9] |
\s |
интервал, табулация, нов ред |
\w |
буква от всеки език, цифра или _ |
. |
какъвто и да е символ (без нов ред) |
Главните букви обръщат смисъла: \D е „не цифра“, \S „не интервал“, \W „не \w“.
В Python \w познава и кирилицата. С флага re.ASCII (трети аргумент на fullmatch) става само латиница, както е в Java и JavaScript:
import re
print(re.fullmatch(r"\w+", "Sofia") is not None)print(re.fullmatch(r"\w+", "София") is not None)print(re.fullmatch(r"\w+", "София", re.ASCII) is not None)print(re.fullmatch(r"[А-Яа-я]+", "София") is not None)TrueTrueFalseTrueПо-точно, за любопитните
[А-Я]е интервал по кодовете на символите. В него влизат и руските Ы и Э, но не и Ё, която стои отделно в таблицата. За българския текст[А-Яа-я]стига. Ако те интересува и „ѝ“, добави го:[А-Яа-яѝЍ].- Истинските български номера ползват само 12 букви, които изглеждат еднакво на кирилица и латиница:
[АВЕКМНОРСТУХ].
Пробвай
Точката
Някой е забравил обратната черта пред точките:
Какво ще отпечата програмата?
import re
pattern = r"\d{2}.\d{2}.\d{4}"print(re.fullmatch(pattern, "05.03.2026") is not None)print(re.fullmatch(pattern, "05-03-2026") is not None)print(re.fullmatch(pattern, "05.03.26") is not None)Без \ точката значи „какъвто и да е символ“, затова тирето също минава и 05-03-2026 е вярно. Третият текст има само две цифри за година, а шаблонът иска четири. Проверката изглежда, че работи, докато не дойде грешен вход. Затова пробвай шаблона и с текстове, които не трябва да минат.
Идеята
Колко пъти
След символ или клас казваш колко пъти може да се повтори. Това е квантификатор:
| Квантификатор | Значи |
|---|---|
? |
нула или едно (може да го има, може да го няма) |
* |
нула или повече |
+ |
едно или повече |
{4} |
точно 4 |
{1,2} |
от 1 до 2 |
{2,} |
2 или повече |
Мобилен номер започва с 0 или +359, после 87, 88 или 89 и още 7 цифри:
import re
mobile = r"(\+359|0)8[789]\d{7}"print(re.fullmatch(mobile, "0888123456") is not None)print(re.fullmatch(mobile, "+359877654321") is not None)print(re.fullmatch(mobile, "0888 123 456") is not None)print(re.fullmatch(mobile, "088812345") is not None)TrueTrueFalseFalse| значи „или“. Скобите държат \+359|0 заедно: без тях „или“ би разделило целия шаблон на две. Скобите правят още неща, които ще видим в следващия урок. + в началото е със \, защото иначе е квантификатор.
Квантификаторът важи и за цяла група в скоби. Цена с дробна част или без нея:
import re
price = r"\d+([.,]\d{1,2})? €"print(re.fullmatch(price, "12 €") is not None)print(re.fullmatch(price, "12.5 €") is not None)print(re.fullmatch(price, "12,50 €") is not None)print(re.fullmatch(price, ".50 €") is not None)TrueTrueTrueFalse([.,]\d{1,2})? значи: „точка или запетая и една-две цифри“, а целият този блок може да го няма.
Идеята
Суров низ: r"…"
Шаблонът минава през два слоя. Първо Python чете низа в кавичките, после re чете шаблона.
В обикновен низ \ започва специален символ: \n е нов ред, \" е кавичка. \d не е такъв. Python 3.12 предупреждава, но оставя \d както е, и програмата работи:
import re
date = "05.03.2026"print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)/shop/date.py:4: SyntaxWarning: invalid escape sequence '\d' print(re.fullmatch("\d{2}\.\d{2}\.\d{4}", date) is not None)TrueРаботи случайно. \b например е специален символ в низа („връщане назад“, backspace), и шаблон без r с него тихо търси друго. Ще го видиш след малко. Затова шаблоните се пишат като сурови низове: с r пред кавичките \ е просто \:
import re
date = "05.03.2026"print(re.fullmatch(r"\d{2}\.\d{2}\.\d{4}", date) is not None)print(re.fullmatch(r"C:\\Temp", "C:\\Temp") is not None)TrueTrue| Искаш в шаблона | Пишеш в Python | Без r |
|---|---|---|
\d (цифра) |
r"\d" |
"\\d" |
\. (истинска точка) |
r"\." |
"\\." |
\\ (истинска обратна черта) |
r"\\" |
"\\\\" |
Символите, които трябва да са със \, когато ги искаш буквално, са . * + ? ( ) [ ] { } | ^ $ \.
Пробвай
Намери грешката
Traceback (most recent call last): File "/shop/price.py", line 5, in <module> euros = int(parts[0]) ^^^^^^^^^^^^^ValueError: invalid literal for int() with base 10: ''Избери реда с грешката.
re.split приема регулярен израз. "." е „всеки символ“, затова всеки символ е разделител и между тях няма нищо: частите са шест празни текста. Грешката излиза чак на следващия ред, когато int получи празен текст. Поправката е re.split(r"\.", price). А тук шаблон изобщо не трябва: price.split(".") (методът на текста) търси обикновен текст.price = "12.50"print(price.split("."))['12', '50']Идеята
Замяна и разделяне по шаблон
re.sub(шаблон, замяна, текст) и re.split(шаблон, текст) работят с шаблон. Така чистиш и разбиваш текст, който хората пишат както им падне:
import re
address = "Доставка: ул. Шипка 12, вход Б"print(re.sub(r"\s+", " ", address))items = re.split(r"[,;\s]+", "хляб, мляко;кафе чай")print(f"{len(items)}: " + " | ".join(items))print(re.sub(r"(?i)summer\d+", "***", "Код SUMMER10, код summer25"))Доставка: ул. Шипка 12, вход Б4: хляб | мляко | кафе | чайКод ***, код ***\s+е „един или повече интервала“: всяка група интервали става един.[,;\s]+е „запетаи, точки и запетаи или интервали, колкото и да са“: разделителят може да е всеки от тях.
(?i)в началото е флаг: шаблонът не гледа главни и малки букви.
Методът на текста replace също заменя всички появи, но търси обикновен текст, не шаблон. Ако не ти трябва шаблон, ползвай него: не трябва да мислиш за точки и обратни черти.
\b е граница на дума: мястото между буква и нещо, което не е буква. С него заменяш само цели думи. В Python \b познава кирилицата, но шаблонът трябва да е суров низ:
import re
text = "стар и старо"print(re.sub("стар", "нов", text))print(re.sub("\bстар\b", "нов", text))print(re.sub(r"\bстар\b", "нов", text))нов и новостар и старонов и староБез \b „старо“ става „ново“. На втория ред няма r: там \b е символът backspace, а не граница, затова не се заменя нищо и грешка няма. Едва със суровия низ на третия ред се заменя само думата „стар“.
По-точно, за любопитните
\Bе обратното на\b: място вътре в дума.r"\Bстар"намира „стар“ само когато не е в началото на дума: в „застаря“, но не в „старо“.^е началото на текста, а$краят:re.sub(r"^0+", "", text)маха водещите нули. Приfullmatchне ти трябват, защото той и без това гледа целия текст.- Флаговете може да се дадат и като константи:
re.IGNORECASEе същото като(?i),re.ASCIIкато(?a). Подават се като последен аргумент:re.sub(шаблон, замяна, текст, flags=re.IGNORECASE). re.split("|", "a|b")е капан като точката:|е „или“ и шаблонът е празен, затова дели между всеки два символа. Методът на текста"a|b".split("|")търси обикновен текст и няма този проблем.
import re
print(re.findall(r"\Bстар", "застаря старо"))print(re.fullmatch(r"(?a)\w+", "София"), re.fullmatch(r"(?i)стар", "СТАР") is not None)print(re.split("|", "a|b"))print("a|b".split("|"))['стар']None True['', 'a', '|', 'b', '']['a', 'b']Пробвай
Пощенски код
Българският пощенски код е точно четири цифри. Какво липсва, за да отпечата програмата True за "1000" и False за "100" или "10000"?
import re
code = "1000"
print(re.fullmatch(r"", code) is not None)\d{4}: точно четири цифри. Понеже fullmatch проверява целия текст, три или пет цифри не минават.Упражнение
Проверки с шаблон
Реши задачите в редактора си. Чети редове, докато има: for line in sys.stdin:, с import sys най-горе. Всеки ред завършва с "\n", затова го махни със strip() или rstrip("\n"). В урока „Файлове“ ще четеш същите редове от файл.
1. Дата. Провери всяка дата дали е във вида дд/мм/гггг. Денят е от 01 до 31, а месецът от 01 до 12. Подсказка: денят е 0[1-9] или [12]\d, или 3[01].
01/01/202032/01/202015/05/202101/01/2020 - Валидна32/01/2020 - Невалидна15/05/2021 - Валидна2. Мобилен номер. Хората пишат номера с интервали и тирета. Махни ги с re.sub и провери това, което остава, с шаблона за мобилен номер от урока.
0888 123 456+359 87 765 432102 812 34560899-12-3450888 123 456 - валиден+359 87 765 4321 - валиден02 812 3456 - невалиден0899-12-345 - невалиден3. Имейл. Провери дали всеки ред прилича на имейл: една или повече латински букви, цифри, ., + или -, после @, после име на домейн с поне една точка (firma.bg, mail.firma.bg). Тук \w би пуснал и кирилица: изброй латиницата с [A-Za-z0-9_].
elena.koleva@firma.bgelena@firma@firma.bgelena koleva@firma.bgelena.koleva@firma.bg - даelena@firma - не@firma.bg - неelena koleva@firma.bg - не4. Списък за пазаруване. Продуктите са разделени със запетаи, точки и запетаи, вертикални черти или интервали, понякога по няколко наведнъж. Отпечатай ги номерирани.
хляб, мляко;кафе чай|сирене1. хляб2. мляко3. кафе4. чай5. сиренеПо-точно, за любопитните
- Шаблонът за дата приема и
31/02/2026. Дали денят съществува в месеца, regex не може да каже разумно. За това има модулътdatetime, а шаблонът е само първото сито:
import datetime
try: datetime.date(2026, 2, 31)except ValueError as e: print(e)day is out of range for month- Пълното правило за имейл адрес (RFC 5322) позволява кавички, коментари и интервали в определени места. Шаблон, който го покрива изцяло, е стотици символи и никой не го поддържа. Истинската проверка е да изпратиш писмо с връзка за потвърждение.
Още задачи
- Парола. Провери дали паролата е поне 8 символа и има поне една цифра. Направи го с два
re.fullmatch:r".{8,}"иr".*\d.*". - Час. Провери час във вида
чч:ммот00:00до23:59. - Само цифри. От
"Тел.: +359 (88) 812-34-56"остави само цифрите и+с единre.subи[^...].
Тест