HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#статьи</a></p>
1 <p><a>#статьи</a></p>
2 <ul><li>5 окт 2022</li>
2 <ul><li>5 окт 2022</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Исчерпывающий гайд по работе с мощным инструментом для анализа и обработки строк.</p>
4 </ul><p>Исчерпывающий гайд по работе с мощным инструментом для анализа и обработки строк.</p>
5 <p>Иллюстрация: Оля Ежак для SKillbox Media</p>
5 <p>Иллюстрация: Оля Ежак для SKillbox Media</p>
6 <p>Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.</p>
6 <p>Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.</p>
7 <p>Само словосочетание "регулярные выражения" звучит непонятно и выглядит страшно, но на самом деле ничего сложного в работе с ними нет. В этой статье мы познакомим вас с их логикой и основными принципами и научим разговаривать на языке шаблонов. В хорошем смысле слова.</p>
7 <p>Само словосочетание "регулярные выражения" звучит непонятно и выглядит страшно, но на самом деле ничего сложного в работе с ними нет. В этой статье мы познакомим вас с их логикой и основными принципами и научим разговаривать на языке шаблонов. В хорошем смысле слова.</p>
8 <p><strong>Содержание:</strong></p>
8 <p><strong>Содержание:</strong></p>
9 <ul><li><a>Что такое регулярные выражения</a></li>
9 <ul><li><a>Что такое регулярные выражения</a></li>
10 <li><a>Синтаксис регулярок</a></li>
10 <li><a>Синтаксис регулярок</a></li>
11 <li><a>Как ведётся поиск</a></li>
11 <li><a>Как ведётся поиск</a></li>
12 <li><a>Квантификаторы и логическое ИЛИ при группировке</a></li>
12 <li><a>Квантификаторы и логическое ИЛИ при группировке</a></li>
13 <li><a>Регулярные выражения в Python: модуль re и Match-объекты</a></li>
13 <li><a>Регулярные выражения в Python: модуль re и Match-объекты</a></li>
14 <li><a>Жадный и ленивый пропуск</a></li>
14 <li><a>Жадный и ленивый пропуск</a></li>
15 <li><a>Примеры и задачи</a></li>
15 <li><a>Примеры и задачи</a></li>
16 </ul><p>Представьте, что вы снова в школе, на уроке истории. Вам нужно решить итоговую контрольную работу по всем датам, которые проходили в четверти.</p>
16 </ul><p>Представьте, что вы снова в школе, на уроке истории. Вам нужно решить итоговую контрольную работу по всем датам, которые проходили в четверти.</p>
17 <p>Но тут вас поджидает препятствие: все даты разбросаны по нескольким главам учебника по десятку страниц каждая. Читать полкниги в поисках нужных вам крупиц информации - такое себе удовольствие. Тем более когда каждая минута на счету.</p>
17 <p>Но тут вас поджидает препятствие: все даты разбросаны по нескольким главам учебника по десятку страниц каждая. Читать полкниги в поисках нужных вам крупиц информации - такое себе удовольствие. Тем более когда каждая минута на счету.</p>
18 <p>К счастью, вы - человек неглупый (не зря же пошли в IT), тренированный и быстро соображающий. Поэтому моментально замечаете основные закономерности:</p>
18 <p>К счастью, вы - человек неглупый (не зря же пошли в IT), тренированный и быстро соображающий. Поэтому моментально замечаете основные закономерности:</p>
19 <ul><li>даты обозначаются цифрами: арабскими, если это год и месяц, и римскими, если век;</li>
19 <ul><li>даты обозначаются цифрами: арабскими, если это год и месяц, и римскими, если век;</li>
20 <li>учебник - по истории позднего Средневековья и Нового времени, поэтому все даты, написанные арабскими цифрами, - четырёхсимвольные;</li>
20 <li>учебник - по истории позднего Средневековья и Нового времени, поэтому все даты, написанные арабскими цифрами, - четырёхсимвольные;</li>
21 <li>после римских цифр всегда идёт слово "век".</li>
21 <li>после римских цифр всегда идёт слово "век".</li>
22 </ul><p>Теперь у вас есть шаблон нужной информации. Остаётся лишь пролистать страницу за страницей и записать даты в смартфон (или себе на подкорку). Вуаля: пятёрка за четверть у вас в дневнике, а премия от родителей за отличную учёбу - в кармане.</p>
22 </ul><p>Теперь у вас есть шаблон нужной информации. Остаётся лишь пролистать страницу за страницей и записать даты в смартфон (или себе на подкорку). Вуаля: пятёрка за четверть у вас в дневнике, а премия от родителей за отличную учёбу - в кармане.</p>
23 <p>По такому же принципу работают и регулярные выражения: они ведут поиск фрагментов текста по определённому шаблону. Если фрагмент совпадает с шаблоном - с ним можно работать.</p>
23 <p>По такому же принципу работают и регулярные выражения: они ведут поиск фрагментов текста по определённому шаблону. Если фрагмент совпадает с шаблоном - с ним можно работать.</p>
24 <p>Запишем логику поиска исторических дат в виде регулярных выражений (они ещё называются Regular Expressions, сокращённо regex или regexp). Выглядеть он будет так:</p>
24 <p>Запишем логику поиска исторических дат в виде регулярных выражений (они ещё называются Regular Expressions, сокращённо regex или regexp). Выглядеть он будет так:</p>
25 (?:\d{4})|(?:[IVX]+ век)<p>Приятные новости: regex - настолько полезный и мощный инструмент, что поддерживается почти всеми современными языками программирования, в том числе и Python. Причём соответствующий синтаксис в разных языках очень схож. Так что, выучив его в одном языке, можно пользоваться им в других, практически не переучиваясь. Поехали.</p>
25 (?:\d{4})|(?:[IVX]+ век)<p>Приятные новости: regex - настолько полезный и мощный инструмент, что поддерживается почти всеми современными языками программирования, в том числе и Python. Причём соответствующий синтаксис в разных языках очень схож. Так что, выучив его в одном языке, можно пользоваться им в других, практически не переучиваясь. Поехали.</p>
26 <p>С помощью regex можно искать как вполне конкретные выражения (например, слово "век" - последовательность букв "в", "е" и "к"), так и что-то более общее (например, любую букву или цифру).</p>
26 <p>С помощью regex можно искать как вполне конкретные выражения (например, слово "век" - последовательность букв "в", "е" и "к"), так и что-то более общее (например, любую букву или цифру).</p>
27 <p>Для обозначения второй категории существуют специальные символы. Вот некоторые из них:</p>
27 <p>Для обозначения второй категории существуют специальные символы. Вот некоторые из них:</p>
28 СимволЧто означает<strong>Пример использования шаблона</strong><strong>Пример вывода</strong>.Любой символ, кроме новой строки (\n)H.llo, .orld<p>20.. год</p>
28 СимволЧто означает<strong>Пример использования шаблона</strong><strong>Пример вывода</strong>.Любой символ, кроме новой строки (\n)H.llo, .orld<p>20.. год</p>
29 Hello, world; Hallo, 2orld<p>2022 год, 2010 год</p>
29 Hello, world; Hallo, 2orld<p>2022 год, 2010 год</p>
30 […]Любой символ из указанных в скобках. Символы можно задавать как перечислением, так и указывая диапазон через дефис[abc123]<p>[A-Z]</p>
30 […]Любой символ из указанных в скобках. Символы можно задавать как перечислением, так и указывая диапазон через дефис[abc123]<p>[A-Z]</p>
31 <p>[A-Za-z0-9]</p>
31 <p>[A-Za-z0-9]</p>
32 <p>[А-ЯЁа-яё]</p>
32 <p>[А-ЯЁа-яё]</p>
33 а; 1<p>B; T</p>
33 а; 1<p>B; T</p>
34 <p>A; s; 1</p>
34 <p>A; s; 1</p>
35 <p>А; ё</p>
35 <p>А; ё</p>
36 [^…]Любой символ, кроме указанных в скобках[^A-Za-z]з, 4^Начало строки^Добрый день,0$Конец строкиДо свидания!$0|Логическое ИЛИ. Регулярное выражение будет искать один из нескольких вариантов[0-9]|[IVXLCDM] - регулярное выражение будет находить совпадение, если цифра является либо арабской, либо римской5; V\Экранирование. Помогает регулярным выражениям ориентироваться, является ли следующий за \ символ обычным или специальным\A\d\w\Z - экранирование превращает буквы алфавита в спецсимволы.<p>\[\.\] - экранирование превращает спецсимволы в обычные</p>
36 [^…]Любой символ, кроме указанных в скобках[^A-Za-z]з, 4^Начало строки^Добрый день,0$Конец строкиДо свидания!$0|Логическое ИЛИ. Регулярное выражение будет искать один из нескольких вариантов[0-9]|[IVXLCDM] - регулярное выражение будет находить совпадение, если цифра является либо арабской, либо римской5; V\Экранирование. Помогает регулярным выражениям ориентироваться, является ли следующий за \ символ обычным или специальным\A\d\w\Z - экранирование превращает буквы алфавита в спецсимволы.<p>\[\.\] - экранирование превращает спецсимволы в обычные</p>
37 0<p><strong>Важное замечание 1.</strong>Регулярные выражения зависимы от регистра, то есть "А" и "а" при поиске будут считаться разными символами.</p>
37 0<p><strong>Важное замечание 1.</strong>Регулярные выражения зависимы от регистра, то есть "А" и "а" при поиске будут считаться разными символами.</p>
38 <p><strong>Важное замечание 2</strong><strong>.</strong>Буквы "Ё" и "ё" не входят в диапазон "А - Я" и "а - я". Так что, задавая русский алфавит, их нужно выписывать отдельно.</p>
38 <p><strong>Важное замечание 2</strong><strong>.</strong>Буквы "Ё" и "ё" не входят в диапазон "А - Я" и "а - я". Так что, задавая русский алфавит, их нужно выписывать отдельно.</p>
39 <p>На экранировании остановимся подробнее. По умолчанию символы .^$*+? {}[]\|() являются спецсимволами - то есть они выполняют определённые функции. Чтобы сделать спецсимволы обычными, их нужно экранировать \.</p>
39 <p>На экранировании остановимся подробнее. По умолчанию символы .^$*+? {}[]\|() являются спецсимволами - то есть они выполняют определённые функции. Чтобы сделать спецсимволы обычными, их нужно экранировать \.</p>
40 <p>Таким образом, . будет обозначать любой символ, а \. - знак точки. Чтобы написать обратный слеш, его тоже нужно экранировать, то есть в регулярных выражениях он будет выглядеть так: \\.</p>
40 <p>Таким образом, . будет обозначать любой символ, а \. - знак точки. Чтобы написать обратный слеш, его тоже нужно экранировать, то есть в регулярных выражениях он будет выглядеть так: \\.</p>
41 <p>Обратная ситуация с некоторыми алфавитными символами. По умолчанию они считаются просто буквами, но при экранировании начинают играть роль спецсимволов.</p>
41 <p>Обратная ситуация с некоторыми алфавитными символами. По умолчанию они считаются просто буквами, но при экранировании начинают играть роль спецсимволов.</p>
42 СимволЧто означает\dЛюбая цифра. То же самое, что [0-9]\DЛюбой символ, кроме цифры. То же самое, что [^0-9]\wЛюбая буква, цифра и нижнее подчёркивание\WЛюбой символ, кроме буквы, цифры и нижнего подчёркивания\sЛюбой пробельный символ (пробел, новая строка, табуляция, возврат каретки и тому подобное)\SЛюбой символ, кроме пробельного\AНачало строки. То же самое, что ^\ZКонец строки. То же самое, что $\bНачало или конец слова\BСередина слова\n, \t\, \rСтандартные строковые обозначения: новая строка, табуляция, возврат каретки<p><strong>Важное замечание.</strong>\<strong>A</strong>, \Z, \b и \B указывают не на конкретный символ, а на положение других символов относительно друг друга. Можно сказать, что они указывают на пространство между символами.</p>
42 СимволЧто означает\dЛюбая цифра. То же самое, что [0-9]\DЛюбой символ, кроме цифры. То же самое, что [^0-9]\wЛюбая буква, цифра и нижнее подчёркивание\WЛюбой символ, кроме буквы, цифры и нижнего подчёркивания\sЛюбой пробельный символ (пробел, новая строка, табуляция, возврат каретки и тому подобное)\SЛюбой символ, кроме пробельного\AНачало строки. То же самое, что ^\ZКонец строки. То же самое, что $\bНачало или конец слова\BСередина слова\n, \t\, \rСтандартные строковые обозначения: новая строка, табуляция, возврат каретки<p><strong>Важное замечание.</strong>\<strong>A</strong>, \Z, \b и \B указывают не на конкретный символ, а на положение других символов относительно друг друга. Можно сказать, что они указывают на пространство между символами.</p>
43 <p>Например, регулярное выражение \<strong>b</strong>[А-ЯЁаяё]\<strong>b</strong>будет искать только те буквы, которые отделены друг от друга пробелами или знаками препинания.</p>
43 <p>Например, регулярное выражение \<strong>b</strong>[А-ЯЁаяё]\<strong>b</strong>будет искать только те буквы, которые отделены друг от друга пробелами или знаками препинания.</p>
44 <p>Часто при записи регулярного выражения какая-то часть шаблона должна повторяться определённое количество раз. Число вхождений в синтаксисе regex задают с помощью квантификаторов. Они всегда помещаются после той части шаблона, которую нужно повторить.</p>
44 <p>Часто при записи регулярного выражения какая-то часть шаблона должна повторяться определённое количество раз. Число вхождений в синтаксисе regex задают с помощью квантификаторов. Они всегда помещаются после той части шаблона, которую нужно повторить.</p>
45 СимволЧто означает<strong>Примеры шаблона</strong>Примеры вывода{}Указывает количество вхождений, можно задавать единичным числом или диапазоном\d{4} - цифра, четыре подряд<p>\d{1,4} - цифра, от одного до четырёх раз подряд</p>
45 СимволЧто означает<strong>Примеры шаблона</strong>Примеры вывода{}Указывает количество вхождений, можно задавать единичным числом или диапазоном\d{4} - цифра, четыре подряд<p>\d{1,4} - цифра, от одного до четырёх раз подряд</p>
46 <p>\d{2,} - цифра, от двух раз подряд</p>
46 <p>\d{2,} - цифра, от двух раз подряд</p>
47 <p>\d{,4} - цифра, от 0 до 4 раз подряд</p>
47 <p>\d{,4} - цифра, от 0 до 4 раз подряд</p>
48 1243, 1876<p>1, 12, 176, 1589</p>
48 1243, 1876<p>1, 12, 176, 1589</p>
49 <p>22, 456, 988888</p>
49 <p>22, 456, 988888</p>
50 <p>5, 15, 987, 1234</p>
50 <p>5, 15, 987, 1234</p>
51 ?От нуля до одного вхождения. То же самое, что {0,1}\d?0*От нуля вхождений. То же самое, что {0,}\d*0+От одного вхождения. То же самое, что {1,}\d+0<p>Теперь давайте ещё раз посмотрим на наше регулярное выражение для поиска дат по учебнику истории:</p>
51 ?От нуля до одного вхождения. То же самое, что {0,1}\d?0*От нуля вхождений. То же самое, что {0,}\d*0+От одного вхождения. То же самое, что {1,}\d+0<p>Теперь давайте ещё раз посмотрим на наше регулярное выражение для поиска дат по учебнику истории:</p>
52 (?:\d{4})|(?:[IVX]+ век)<p>В нём есть несколько дополнительных символов, о которых рассказано ниже, но начинка этого выражения уже понятна.</p>
52 (?:\d{4})|(?:[IVX]+ век)<p>В нём есть несколько дополнительных символов, о которых рассказано ниже, но начинка этого выражения уже понятна.</p>
53 <ul><li>\d{4} - цифра, четыре подряд</li>
53 <ul><li>\d{4} - цифра, четыре подряд</li>
54 <li>| - логическое ИЛИ</li>
54 <li>| - логическое ИЛИ</li>
55 <li>[IVX]+ век - символ I, V или X, одно или более вхождений, пробел, слово "век"</li>
55 <li>[IVX]+ век - символ I, V или X, одно или более вхождений, пробел, слово "век"</li>
56 </ul><p>Попрактиковаться в составлении регулярных выражений можно на сайте<a>regex101.com</a>. А мы разберём основные приёмы их использования и решим несколько задач.</p>
56 </ul><p>Попрактиковаться в составлении регулярных выражений можно на сайте<a>regex101.com</a>. А мы разберём основные приёмы их использования и решим несколько задач.</p>
57 <p>Уточним ещё несколько терминов regex.</p>
57 <p>Уточним ещё несколько терминов regex.</p>
58 <p>Регулярные выражения - это инструмент для работы со строками, которые и являются основной их единицей.</p>
58 <p>Регулярные выражения - это инструмент для работы со строками, которые и являются основной их единицей.</p>
59 <p>Строка представляет собой как само регулярное выражение, так и текст, по которому ведётся поиск.</p>
59 <p>Строка представляет собой как само регулярное выражение, так и текст, по которому ведётся поиск.</p>
60 <p>Найденные в тексте совпадения с шаблоном называются подстроками. Например, у нас есть регулярное выражение м. (буква "м", затем любой символ) и текст "Мама мыла раму". Применяя регулярное выражение к тексту, мы найдём подстроки "ма", "мы" и "му". Подстроку "Ма" наше выражение пропустит из-за разницы в регистре.</p>
60 <p>Найденные в тексте совпадения с шаблоном называются подстроками. Например, у нас есть регулярное выражение м. (буква "м", затем любой символ) и текст "Мама мыла раму". Применяя регулярное выражение к тексту, мы найдём подстроки "ма", "мы" и "му". Подстроку "Ма" наше выражение пропустит из-за разницы в регистре.</p>
61 <p>Есть и более мелкая единица, чем подстрока, - группа. Она представляет собой часть подстроки, которую мы попросили выделить специально. Группы выделяются круглыми скобками (…).</p>
61 <p>Есть и более мелкая единица, чем подстрока, - группа. Она представляет собой часть подстроки, которую мы попросили выделить специально. Группы выделяются круглыми скобками (…).</p>
62 <p>Возьмём ту же строку "Мама мыла раму" и применим к ней следующее регулярное выражение:</p>
62 <p>Возьмём ту же строку "Мама мыла раму" и применим к ней следующее регулярное выражение:</p>
63 (\w)(\w{3})<p>Оно значит: буквенный символ, выделенный группой, и за ним ещё три буквенных символа, также выделенных группой. Итого весь шаблон представляет собой четыре буквенных символа.</p>
63 (\w)(\w{3})<p>Оно значит: буквенный символ, выделенный группой, и за ним ещё три буквенных символа, также выделенных группой. Итого весь шаблон представляет собой четыре буквенных символа.</p>
64 <p>В нашем тексте это выражение найдёт три совпадения, в каждом из которых выделит две группы:</p>
64 <p>В нашем тексте это выражение найдёт три совпадения, в каждом из которых выделит две группы:</p>
65 ПодстрокаГруппа 1Группа 2МамаМамамыламыларамураму<p>Это помогает извлечь из найденной подстроки конкретную информацию, отбросив всё остальное. Например, мы нашли адрес, состоящий из названия улицы, номера дома и номера квартиры. Подстрока будет представлять собой адрес целиком, а в группы можно поместить отдельно каждый его структурный элемент - и потом обращаться к нему напрямую.</p>
65 ПодстрокаГруппа 1Группа 2МамаМамамыламыларамураму<p>Это помогает извлечь из найденной подстроки конкретную информацию, отбросив всё остальное. Например, мы нашли адрес, состоящий из названия улицы, номера дома и номера квартиры. Подстрока будет представлять собой адрес целиком, а в группы можно поместить отдельно каждый его структурный элемент - и потом обращаться к нему напрямую.</p>
66 <p>Группам можно давать имена с помощью такой формы: (? P&lt;name&gt;…)</p>
66 <p>Группам можно давать имена с помощью такой формы: (? P&lt;name&gt;…)</p>
67 <p>Вот так будет выглядеть наш шаблон, ищущий четырёхбуквенные слова, если мы дадим имена группам:</p>
67 <p>Вот так будет выглядеть наш шаблон, ищущий четырёхбуквенные слова, если мы дадим имена группам:</p>
68 ?P&lt;first_letter&gt;\w)(?P&lt;rest_letters&gt;\w{3})<p>Уберём группы и упростим регулярное выражение, чтобы оно искало только подстроку:</p>
68 ?P&lt;first_letter&gt;\w)(?P&lt;rest_letters&gt;\w{3})<p>Уберём группы и упростим регулярное выражение, чтобы оно искало только подстроку:</p>
69 \w{4}<p>Немного изменим текст, по которому ищем совпадения: "Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке".</p>
69 \w{4}<p>Немного изменим текст, по которому ищем совпадения: "Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке".</p>
70 <p>Регулярное выражение ищет четыре буквенных символа подряд, поэтому в качестве отдельных подстрок находит также "пило", "раме", "рабо", "тает", "лесо" и "пилк".</p>
70 <p>Регулярное выражение ищет четыре буквенных символа подряд, поэтому в качестве отдельных подстрок находит также "пило", "раме", "рабо", "тает", "лесо" и "пилк".</p>
71 <p>Исправьте регулярное выражение так, чтобы оно находило только четырёхбуквенные слова. То есть оно должно найти подстроки "мама", "мыла", "раму" и "папа" - и ничего больше.</p>
71 <p>Исправьте регулярное выражение так, чтобы оно находило только четырёхбуквенные слова. То есть оно должно найти подстроки "мама", "мыла", "раму" и "папа" - и ничего больше.</p>
72 <p><strong>Подсказка, если не можете решить задачу</strong></p>
72 <p><strong>Подсказка, если не можете решить задачу</strong></p>
73 <p>Используйте символ \b.</p>
73 <p>Используйте символ \b.</p>
74 <p><strong>Важное замечание.</strong>При написании regex нужно помнить, что они ищут только непересекающиеся подстроки. Под шаблон \w{4} в слове "работает" подходят не только подстроки "рабо" и "тает", но и "абот", "бота", "отае". Их регулярное выражение не находит, потому что тогда бы эти подстроки пересеклись с другими - а в regex так нельзя.</p>
74 <p><strong>Важное замечание.</strong>При написании regex нужно помнить, что они ищут только непересекающиеся подстроки. Под шаблон \w{4} в слове "работает" подходят не только подстроки "рабо" и "тает", но и "абот", "бота", "отае". Их регулярное выражение не находит, потому что тогда бы эти подстроки пересеклись с другими - а в regex так нельзя.</p>
75 <p>Нередко при использовании регулярных выражений требуется применить квантификатор либо логическое ИЛИ не к отдельному символу, а к целой группе. Именно так мы поступили в нашем шаблоне для поиска дат по учебнику истории:</p>
75 <p>Нередко при использовании регулярных выражений требуется применить квантификатор либо логическое ИЛИ не к отдельному символу, а к целой группе. Именно так мы поступили в нашем шаблоне для поиска дат по учебнику истории:</p>
76 (?:\d{4})|(?:[IVX]+ век)<p>С помощью скобок мы сказали: выдайте совпадение, если в тексте присутствует хотя бы один из двух вариантов - либо год, либо век.</p>
76 (?:\d{4})|(?:[IVX]+ век)<p>С помощью скобок мы сказали: выдайте совпадение, если в тексте присутствует хотя бы один из двух вариантов - либо год, либо век.</p>
77 <p><strong>Важное замечание.</strong>? : в начале группы означает, что мы просим regex не запоминать эту группу. Если все группы открываются символами? :, то регулярные выражения вернут только подстроку и ни одной группы.</p>
77 <p><strong>Важное замечание.</strong>? : в начале группы означает, что мы просим regex не запоминать эту группу. Если все группы открываются символами? :, то регулярные выражения вернут только подстроку и ни одной группы.</p>
78 <p>В Python это может быть полезно, потому что некоторые re-функции возвращают разные результаты в зависимости от того, запомнили ли регулярные выражения какие-то группы или нет.</p>
78 <p>В Python это может быть полезно, потому что некоторые re-функции возвращают разные результаты в зависимости от того, запомнили ли регулярные выражения какие-то группы или нет.</p>
79 <p>Также к группам удобно применять квантификаторы. Например, имена многих дроидов в "Звёздных войнах" построены по принципу: буква - цифра - буква - цифра.</p>
79 <p>Также к группам удобно применять квантификаторы. Например, имена многих дроидов в "Звёздных войнах" построены по принципу: буква - цифра - буква - цифра.</p>
80 <p>Вот так это выглядит без групп:</p>
80 <p>Вот так это выглядит без групп:</p>
81 [A-Z]\d[A-Z]\d<p>И вот так с ними:</p>
81 [A-Z]\d[A-Z]\d<p>И вот так с ними:</p>
82 (?:[A-Z]\d){2}<p>Особенно полезно использовать незапоминаемые группы со сложными шаблонами.</p>
82 (?:[A-Z]\d){2}<p>Особенно полезно использовать незапоминаемые группы со сложными шаблонами.</p>
83 <p>Чтобы работать с регулярными выражениями в Python, необходимо импортировать модуль<strong>re</strong>:</p>
83 <p>Чтобы работать с регулярными выражениями в Python, необходимо импортировать модуль<strong>re</strong>:</p>
84 import re<p>Это даёт доступ к нескольким функциям. Вот их краткое описание.</p>
84 import re<p>Это даёт доступ к нескольким функциям. Вот их краткое описание.</p>
85 ФункцияЧто делает<strong>Если находит совпадение</strong><strong>Если не находит совпадение</strong>re.match (pattern, string)Ищет pattern в начале строки stringВозвращает Match-объектВозвращает Nonere.search (pattern, string)Ищет pattern по всей строке stringВозвращает Match-объект с первым совпадением, остальные не находитВозвращает Nonere.finditer (pattern, string)Ищет pattern по всей строке stringВозвращает итератор, содержащий Match-объекты для каждого найденного совпаденияВозвращает пустой итераторre.findall (pattern, string)Ищет pattern по всей строке stringВозвращает список со всеми найденными совпадениямиВозвращает Nonere.split (pattern, string, [maxsplit=0])Разделяет строку string по подстрокам, соответствующим patternВозвращает список строк, на которые разделила исходную строкуВозвращает список строк, единственный элемент которого - неразделённая исходная строкаre.sub (pattern, repl, string)Заменяет в строке string все pattern на replВозвращает строку в изменённом видеВозвращает строку в исходном видеre.compile (pattern)Собирает регулярное выражение в объект для будущего использования в других re-функцияхНичего не ищет, всегда возвращает Pattern-объект0<p><strong>Важное замечание.</strong>Напоминаем, что регулярные выражения по умолчанию ищут только непересекающиеся подстроки.</p>
85 ФункцияЧто делает<strong>Если находит совпадение</strong><strong>Если не находит совпадение</strong>re.match (pattern, string)Ищет pattern в начале строки stringВозвращает Match-объектВозвращает Nonere.search (pattern, string)Ищет pattern по всей строке stringВозвращает Match-объект с первым совпадением, остальные не находитВозвращает Nonere.finditer (pattern, string)Ищет pattern по всей строке stringВозвращает итератор, содержащий Match-объекты для каждого найденного совпаденияВозвращает пустой итераторre.findall (pattern, string)Ищет pattern по всей строке stringВозвращает список со всеми найденными совпадениямиВозвращает Nonere.split (pattern, string, [maxsplit=0])Разделяет строку string по подстрокам, соответствующим patternВозвращает список строк, на которые разделила исходную строкуВозвращает список строк, единственный элемент которого - неразделённая исходная строкаre.sub (pattern, repl, string)Заменяет в строке string все pattern на replВозвращает строку в изменённом видеВозвращает строку в исходном видеre.compile (pattern)Собирает регулярное выражение в объект для будущего использования в других re-функцияхНичего не ищет, всегда возвращает Pattern-объект0<p><strong>Важное замечание.</strong>Напоминаем, что регулярные выражения по умолчанию ищут только непересекающиеся подстроки.</p>
86 <p>Для написания регулярных выражений в Python используют r-строки (их называют сырыми, или необработанными). Это связано с тем, что написание знака \ требует экранирования не только в регулярных выражениях, но и в самом Python тоже.</p>
86 <p>Для написания регулярных выражений в Python используют r-строки (их называют сырыми, или необработанными). Это связано с тем, что написание знака \ требует экранирования не только в регулярных выражениях, но и в самом Python тоже.</p>
87 <p>Чтобы программистам не приходилось экранировать экранирование и писать нагромождения обратных слешей, и придумали r-строки. Синтаксически они обозначаются так:</p>
87 <p>Чтобы программистам не приходилось экранировать экранирование и писать нагромождения обратных слешей, и придумали r-строки. Синтаксически они обозначаются так:</p>
88 r'...'<p>Перечислим самые популярные из них.</p>
88 r'...'<p>Перечислим самые популярные из них.</p>
89 <p>Находит совпадение только в том случае, если соответствующая шаблону подстрока находится в начале строки, по которой ведётся поиск:</p>
89 <p>Находит совпадение только в том случае, если соответствующая шаблону подстрока находится в начале строки, по которой ведётся поиск:</p>
90 print (re.match (r'Мама', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (re.match (r'мыла', 'Мама мыла раму')) &gt;&gt;&gt; None<p>Как видим, поиск по шаблону "Мама" нашёл совпадение и вернул Match-объект. Слово же "мыла", хотя и есть в строке, находится не в начале. Поэтому регулярное выражение ничего не находит и возвращается None.</p>
90 print (re.match (r'Мама', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (re.match (r'мыла', 'Мама мыла раму')) &gt;&gt;&gt; None<p>Как видим, поиск по шаблону "Мама" нашёл совпадение и вернул Match-объект. Слово же "мыла", хотя и есть в строке, находится не в начале. Поэтому регулярное выражение ничего не находит и возвращается None.</p>
91 <p>Ищет совпадения по всему тексту:</p>
91 <p>Ищет совпадения по всему тексту:</p>
92 print (re.search (r'Мама', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (re.search (r'мыла', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt;<p>При этом re.search возвращает только первое совпадение, даже если в строке, по которой ведётся поиск, их больше. Проверим это:</p>
92 print (re.search (r'Мама', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (re.search (r'мыла', 'Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt;<p>При этом re.search возвращает только первое совпадение, даже если в строке, по которой ведётся поиск, их больше. Проверим это:</p>
93 print (re.search (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла')) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt;<p>Возвращает итератор с объектами, к которым можно обратиться через цикл:</p>
93 print (re.search (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла')) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt;<p>Возвращает итератор с объектами, к которым можно обратиться через цикл:</p>
94 results = re.finditer (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла') print (results) &gt;&gt;&gt; &lt;callable_iterator object at 0x000001C4CDE446D0&gt; for match in results: print (match) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt; &gt;&gt;&gt; &lt;re.Match object; span=(32, 36), match='мыла'&gt; &gt;&gt;&gt; &lt;re.Match object; span=(54, 58), match='мыла'&gt;<p>Эта функция очень полезна, если вы хотите получить Match-объект для каждого совпадения.</p>
94 results = re.finditer (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла') print (results) &gt;&gt;&gt; &lt;callable_iterator object at 0x000001C4CDE446D0&gt; for match in results: print (match) &gt;&gt;&gt; &lt;re.Match object; span=(5, 9), match='мыла'&gt; &gt;&gt;&gt; &lt;re.Match object; span=(32, 36), match='мыла'&gt; &gt;&gt;&gt; &lt;re.Match object; span=(54, 58), match='мыла'&gt;<p>Эта функция очень полезна, если вы хотите получить Match-объект для каждого совпадения.</p>
95 <p>В Match-объектах хранится много всего интересного. Посмотрим внимательнее на объект с подстрокой "Мама", который нашла функция re.match:</p>
95 <p>В Match-объектах хранится много всего интересного. Посмотрим внимательнее на объект с подстрокой "Мама", который нашла функция re.match:</p>
96 &lt;re.Match object; span=(0, 4), match='Мама'&gt;<p><strong>span</strong> - это индекс начала и конца найденной подстроки в тексте, по которому мы искали совпадение. Обратите внимание, что второй индекс не включается в подстроку.</p>
96 &lt;re.Match object; span=(0, 4), match='Мама'&gt;<p><strong>span</strong> - это индекс начала и конца найденной подстроки в тексте, по которому мы искали совпадение. Обратите внимание, что второй индекс не включается в подстроку.</p>
97 <p><strong>match</strong> - это собственно найденная подстрока. Если подстрока длинная, то она будет отображаться не целиком.</p>
97 <p><strong>match</strong> - это собственно найденная подстрока. Если подстрока длинная, то она будет отображаться не целиком.</p>
98 <p>Это, конечно же, не всё, что можно получить от Match-объекта. Рассмотрим ещё несколько методов.</p>
98 <p>Это, конечно же, не всё, что можно получить от Match-объекта. Рассмотрим ещё несколько методов.</p>
99 <p>Возвращает найденную подстроку, если ему не передавать аргумент или передать аргумент 0. То же самое делает обращение к объекту по индексу 0:</p>
99 <p>Возвращает найденную подстроку, если ему не передавать аргумент или передать аргумент 0. То же самое делает обращение к объекту по индексу 0:</p>
100 match = re.match (r'Мама', 'Мама мыла раму') print (match.group()) &gt;&gt;&gt; Мама print (match.group(0)) &gt;&gt;&gt; Мама print (match[0]) &gt;&gt;&gt; Мама<p>Если регулярное выражение поделено на группы, то, начиная с единицы, можно вызвать группу отдельно от строки:</p>
100 match = re.match (r'Мама', 'Мама мыла раму') print (match.group()) &gt;&gt;&gt; Мама print (match.group(0)) &gt;&gt;&gt; Мама print (match[0]) &gt;&gt;&gt; Мама<p>Если регулярное выражение поделено на группы, то, начиная с единицы, можно вызвать группу отдельно от строки:</p>
101 match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.group(1)) print (match.group(2)) &gt;&gt;&gt; М &gt;&gt;&gt; ама print (match[1]) print (match[2]) &gt;&gt;&gt; М &gt;&gt;&gt; ама #Методом group также можно получить кортеж из нужных групп. print (match.group(1,2)) &gt;&gt;&gt; ('М', 'ама')<p>Если группы поименованы, то в качестве аргумента метода group можно передавать их название:</p>
101 match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.group(1)) print (match.group(2)) &gt;&gt;&gt; М &gt;&gt;&gt; ама print (match[1]) print (match[2]) &gt;&gt;&gt; М &gt;&gt;&gt; ама #Методом group также можно получить кортеж из нужных групп. print (match.group(1,2)) &gt;&gt;&gt; ('М', 'ама')<p>Если группы поименованы, то в качестве аргумента метода group можно передавать их название:</p>
102 match = re.match (r'(?P&lt;first_letter&gt;М)(?P&lt;rest_letters&gt;ама)', 'Мама мыла раму') print (match.group('first_letter')) print (match.group('rest_letters')) &gt;&gt;&gt; М &gt;&gt;&gt; ама<p>Если одна и та же группа соответствует шаблону несколько раз, то в группу запишется только последнее совпадение:</p>
102 match = re.match (r'(?P&lt;first_letter&gt;М)(?P&lt;rest_letters&gt;ама)', 'Мама мыла раму') print (match.group('first_letter')) print (match.group('rest_letters')) &gt;&gt;&gt; М &gt;&gt;&gt; ама<p>Если одна и та же группа соответствует шаблону несколько раз, то в группу запишется только последнее совпадение:</p>
103 #Помещаем в группу один буквенный символ, при этом шаблон представляет собой четыре таких символа. match = re.match (r'(\w){4}', 'Мама мыла раму') print (match.group(0)) &gt;&gt;&gt; Мама print (match.group(1)) &gt;&gt;&gt; а<p>Возвращает кортеж с группами:</p>
103 #Помещаем в группу один буквенный символ, при этом шаблон представляет собой четыре таких символа. match = re.match (r'(\w){4}', 'Мама мыла раму') print (match.group(0)) &gt;&gt;&gt; Мама print (match.group(1)) &gt;&gt;&gt; а<p>Возвращает кортеж с группами:</p>
104 match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.groups()) &gt;&gt;&gt; ('М', 'ама')<p>Возвращает кортеж с индексом начала и конца подстроки в исходном тексте. Если мы хотим получить только первый индекс, можно использовать метод<strong>start</strong>, только последний -<strong>end</strong>:</p>
104 match = re.match (r'(М)(ама)', 'Мама мыла раму') print (match.groups()) &gt;&gt;&gt; ('М', 'ама')<p>Возвращает кортеж с индексом начала и конца подстроки в исходном тексте. Если мы хотим получить только первый индекс, можно использовать метод<strong>start</strong>, только последний -<strong>end</strong>:</p>
105 match = re.search (r'мыла', 'Мама мыла раму') print (match.span()) &gt;&gt;&gt; (5, 9) print (match.start()) &gt;&gt;&gt; 5 print (match.end()) &gt;&gt;&gt; 9<p>Возвращает просто список совпадений. Никаких Match-объектов, к которым нужно дополнительно обращаться:</p>
105 match = re.search (r'мыла', 'Мама мыла раму') print (match.span()) &gt;&gt;&gt; (5, 9) print (match.start()) &gt;&gt;&gt; 5 print (match.end()) &gt;&gt;&gt; 9<p>Возвращает просто список совпадений. Никаких Match-объектов, к которым нужно дополнительно обращаться:</p>
106 #В этом примере в качестве регулярного выражения мы используем правильный ответ на задание 0. match_list = re.findall (r'\b\w{4}\b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) &gt;&gt;&gt; ['Мама', 'мыла', 'раму', 'папа']<p>Функция ведёт себя по-другому, если в регулярном выражении есть деление на группы. Тогда функция возвращает список кортежей с группами:</p>
106 #В этом примере в качестве регулярного выражения мы используем правильный ответ на задание 0. match_list = re.findall (r'\b\w{4}\b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) &gt;&gt;&gt; ['Мама', 'мыла', 'раму', 'папа']<p>Функция ведёт себя по-другому, если в регулярном выражении есть деление на группы. Тогда функция возвращает список кортежей с группами:</p>
107 match_list = re.findall (r'\b(\w{1})(\w{3})\b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) &gt;&gt;&gt; [('М', 'ама'), ('м', 'ыла'), ('р', 'аму'), ('п', 'апа')]<p>Аналог метода str.split. Делит исходную строку по шаблону, а сам шаблон исключает из результата:</p>
107 match_list = re.findall (r'\b(\w{1})(\w{3})\b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (match_list) &gt;&gt;&gt; [('М', 'ама'), ('м', 'ыла'), ('р', 'аму'), ('п', 'апа')]<p>Аналог метода str.split. Делит исходную строку по шаблону, а сам шаблон исключает из результата:</p>
108 #Поделим строку по запятой и пробелу после неё. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму', 'а папа был на пилораме', 'потому что работает на лесопилке.']<p>re.split также имеет дополнительный аргумент maxsplit - это максимальное количество частей, на которые функция может поделить строку. По умолчанию maxsplit равен нулю, то есть не устанавливает никаких ограничений:</p>
108 #Поделим строку по запятой и пробелу после неё. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму', 'а папа был на пилораме', 'потому что работает на лесопилке.']<p>re.split также имеет дополнительный аргумент maxsplit - это максимальное количество частей, на которые функция может поделить строку. По умолчанию maxsplit равен нулю, то есть не устанавливает никаких ограничений:</p>
109 #Приравняем аргумент maxsplit к единице. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.', maxsplit=1) print (split_string) &gt;&gt;&gt; ['Мама мыла раму', 'а папа был на пилораме, потому что работает на лесопилке.']<p>Если в re.split мы указываем группы, то они попадают в список строк в качестве отдельных элементов. Для наглядности поделим исходную строку на слог "па":</p>
109 #Приравняем аргумент maxsplit к единице. split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.', maxsplit=1) print (split_string) &gt;&gt;&gt; ['Мама мыла раму', 'а папа был на пилораме, потому что работает на лесопилке.']<p>Если в re.split мы указываем группы, то они попадают в список строк в качестве отдельных элементов. Для наглядности поделим исходную строку на слог "па":</p>
110 #Помещаем буквы "п" и "а" в одну группу. split_string = re.split (r'(па)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму, а ', 'па', '', 'па', ' был на пилораме, потому что работает на лесопилке.'] #Помещаем буквы "п" и "а" в разные группы. split_string = re.split (r'(п)(а)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму, а ', 'п', 'а', '', 'п', 'а', ' был на пилораме, потому что работает на лесопилке.']<p>Требует указания дополнительного аргумента в виде строки, на которую и будет заменять найденные совпадения:</p>
110 #Помещаем буквы "п" и "а" в одну группу. split_string = re.split (r'(па)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму, а ', 'па', '', 'па', ' был на пилораме, потому что работает на лесопилке.'] #Помещаем буквы "п" и "а" в разные группы. split_string = re.split (r'(п)(а)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (split_string) &gt;&gt;&gt; ['Мама мыла раму, а ', 'п', 'а', '', 'п', 'а', ' был на пилораме, потому что работает на лесопилке.']<p>Требует указания дополнительного аргумента в виде строки, на которую и будет заменять найденные совпадения:</p>
111 new_string = re.sub (r'Мама', 'Дочка', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (new_string) &gt;&gt;&gt; Дочка мыла раму, а папа был на пилораме, потому что работает на лесопилке.<p>Дополнительные возможности у функции появляются при применении групп. В качестве аргумента замены ему можно передать не строку, а ссылку на номер группы в виде \n. Тогда он подставит на нужное место соответствующую группу из шаблона. Это очень удобно, когда нужно поменять местами структурные элементы в тексте:</p>
111 new_string = re.sub (r'Мама', 'Дочка', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.') print (new_string) &gt;&gt;&gt; Дочка мыла раму, а папа был на пилораме, потому что работает на лесопилке.<p>Дополнительные возможности у функции появляются при применении групп. В качестве аргумента замены ему можно передать не строку, а ссылку на номер группы в виде \n. Тогда он подставит на нужное место соответствующую группу из шаблона. Это очень удобно, когда нужно поменять местами структурные элементы в тексте:</p>
112 new_string = re.sub (r'(\w+) (\w+) (\w+),', r'\2 \3 \1 -', 'Бендер Остап Ибрагимович, директор ООО "Рога и копыта"') print (new_string) &gt;&gt;&gt; Остап Ибрагимович Бендер - директор ООО "Рога и копыта"<p>Используется для ускорения и упрощения кода, когда одно и то же регулярное выражение применяется в нём несколько раз. Её синтаксис выглядит так:</p>
112 new_string = re.sub (r'(\w+) (\w+) (\w+),', r'\2 \3 \1 -', 'Бендер Остап Ибрагимович, директор ООО "Рога и копыта"') print (new_string) &gt;&gt;&gt; Остап Ибрагимович Бендер - директор ООО "Рога и копыта"<p>Используется для ускорения и упрощения кода, когда одно и то же регулярное выражение применяется в нём несколько раз. Её синтаксис выглядит так:</p>
113 pattern = re.compile (r'Мама') print (pattern.search ('Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (pattern.sub ('Дочка', 'Мама мыла раму')) &gt;&gt;&gt; Дочка мыла раму<p>Нередко в регулярных выражениях нужно учесть сразу много вариантов и опций, из-за чего их структура усложняется. А regex даже простые и короткие читать нелегко, что уж говорить о длинных.</p>
113 pattern = re.compile (r'Мама') print (pattern.search ('Мама мыла раму')) &gt;&gt;&gt; &lt;re.Match object; span=(0, 4), match='Мама'&gt; print (pattern.sub ('Дочка', 'Мама мыла раму')) &gt;&gt;&gt; Дочка мыла раму<p>Нередко в регулярных выражениях нужно учесть сразу много вариантов и опций, из-за чего их структура усложняется. А regex даже простые и короткие читать нелегко, что уж говорить о длинных.</p>
114 <p>Чтобы хоть как-то облегчить чтение регулярок, в Python r-строки можно делить точно так же, как и обычные. Возьмём наше выражение для поиска дат по учебнику истории:</p>
114 <p>Чтобы хоть как-то облегчить чтение регулярок, в Python r-строки можно делить точно так же, как и обычные. Возьмём наше выражение для поиска дат по учебнику истории:</p>
115 re.findall (r'(?:\d{4})|(?:[IVX]+ век)', text)<p>Его же можно написать вот в таком виде:</p>
115 re.findall (r'(?:\d{4})|(?:[IVX]+ век)', text)<p>Его же можно написать вот в таком виде:</p>
116 re.findall (r'(?:\d{4})' r'|' r'(?:[IVX]+ век)', text)<p>Часто при написании регулярных выражений приходится использовать квантификаторы, охватывающие диапазон значений. Например, \d{1,4}. Как регулярные выражения решают, сколько цифр им захватить, одну или четыре? Это определяется пропуском квантификаторов.</p>
116 re.findall (r'(?:\d{4})' r'|' r'(?:[IVX]+ век)', text)<p>Часто при написании регулярных выражений приходится использовать квантификаторы, охватывающие диапазон значений. Например, \d{1,4}. Как регулярные выражения решают, сколько цифр им захватить, одну или четыре? Это определяется пропуском квантификаторов.</p>
117 <p>По умолчанию все квантификаторы являются жадными, то есть стараются захватить столько подходящих под шаблон символов, сколько смогут.</p>
117 <p>По умолчанию все квантификаторы являются жадными, то есть стараются захватить столько подходящих под шаблон символов, сколько смогут.</p>
118 <p>В некоторых случаях это может стать проблемой. Например, возьмём часть оглавления поэмы Венедикта Ерофеева "Москва - Петушки", записанную в одну строку:</p>
118 <p>В некоторых случаях это может стать проблемой. Например, возьмём часть оглавления поэмы Венедикта Ерофеева "Москва - Петушки", записанную в одну строку:</p>
119 <p>Фрязево - 61-й километр..........64 61-й километр - 65-й километр…68 65-й километр - Павлово-Посад…71 Павлово-Посад - Назарьево........73 Назарьево - Дрезна...............77 Дрезна - 85-й километр...........80</p>
119 <p>Фрязево - 61-й километр..........64 61-й километр - 65-й километр…68 65-й километр - Павлово-Посад…71 Павлово-Посад - Назарьево........73 Назарьево - Дрезна...............77 Дрезна - 85-й километр...........80</p>
120 <p>Нужно написать регулярное выражение, которое выделит каждый пункт оглавления. Для этого определим признаки, по которым мы будем это делать:</p>
120 <p>Нужно написать регулярное выражение, которое выделит каждый пункт оглавления. Для этого определим признаки, по которым мы будем это делать:</p>
121 <ul><li>Каждый пункт начинается с буквы или цифры (для этого используем шаблон \w).</li>
121 <ul><li>Каждый пункт начинается с буквы или цифры (для этого используем шаблон \w).</li>
122 <li>Он может содержать внутри себя любой набор символов: буквы, цифры, знаки препинания (для этого используем шаблон .+).</li>
122 <li>Он может содержать внутри себя любой набор символов: буквы, цифры, знаки препинания (для этого используем шаблон .+).</li>
123 <li>Он заканчивается на точку, после которой следует от одной до трёх цифр (для этого используем шаблон \.\d{1,3}).</li>
123 <li>Он заканчивается на точку, после которой следует от одной до трёх цифр (для этого используем шаблон \.\d{1,3}).</li>
124 </ul><p>Посмотрим в конструкторе, как работает наше выражение:</p>
124 </ul><p>Посмотрим в конструкторе, как работает наше выражение:</p>
125 <em>Скриншот: Skillbox Media</em><p>Что же произошло? Почему найдено только одно совпадение, причем за него посчитали весь текст сразу? Всё дело в жадности квантификатора +, который старается захватить максимально возможное количество подходящих символов.</p>
125 <em>Скриншот: Skillbox Media</em><p>Что же произошло? Почему найдено только одно совпадение, причем за него посчитали весь текст сразу? Всё дело в жадности квантификатора +, который старается захватить максимально возможное количество подходящих символов.</p>
126 <p>В итоге шаблон \w находит совпадение с буквой "Ф" в начале текста, шаблон \.\d{1,3} находит совпадение с ".80" в конце текста, а всё, что между ними, покрывается шаблоном .+.</p>
126 <p>В итоге шаблон \w находит совпадение с буквой "Ф" в начале текста, шаблон \.\d{1,3} находит совпадение с ".80" в конце текста, а всё, что между ними, покрывается шаблоном .+.</p>
127 <p>Чтобы квантификатор захватывал минимально возможное количество символов, его нужно сделать ленивым. В таком случае каждый раз, находя совпадение с шаблоном ., регулярное выражение будет спрашивать: "Подходят ли следующие символы в строке под оставшуюся часть шаблона?"</p>
127 <p>Чтобы квантификатор захватывал минимально возможное количество символов, его нужно сделать ленивым. В таком случае каждый раз, находя совпадение с шаблоном ., регулярное выражение будет спрашивать: "Подходят ли следующие символы в строке под оставшуюся часть шаблона?"</p>
128 <p>Если нет, то функция будет искать следующее совпадение с .. А если да, то . закончит свою работу и следующие символы строки будут сравниваться со следующей частью регулярного выражения: \.\d{1,3}.</p>
128 <p>Если нет, то функция будет искать следующее совпадение с .. А если да, то . закончит свою работу и следующие символы строки будут сравниваться со следующей частью регулярного выражения: \.\d{1,3}.</p>
129 <p>Чтобы объявить квантификатор ленивым, после него надо поставить символ ?. Сделаем ленивым квантификатор + в нашем регулярном выражении для поиска строк в оглавлении:</p>
129 <p>Чтобы объявить квантификатор ленивым, после него надо поставить символ ?. Сделаем ленивым квантификатор + в нашем регулярном выражении для поиска строк в оглавлении:</p>
130 <em>Скриншот: Skillbox Media</em><p>Теперь, когда мы уверены в правильности работы нашего регулярного выражения, используем функцию re.findall, чтобы выписать оглавление построчно:</p>
130 <em>Скриншот: Skillbox Media</em><p>Теперь, когда мы уверены в правильности работы нашего регулярного выражения, используем функцию re.findall, чтобы выписать оглавление построчно:</p>
131 content = 'Фрязево - 61-й километр..........64 61-й километр - 65-й километр....68 65-й километр - Павлово-Посад....71 Павлово-Посад - Назарьево........73 Назарьево - Дрезна...............77 Дрезна - 85-й километр...........80' strings = re.findall (r'\w.+?\.\d{1,3}', content) for string in strings: print (string) #Результат на экране. &gt;&gt;&gt; Фрязево - 61-й километр..........64 &gt;&gt;&gt; 61-й километр - 65-й километр....68 &gt;&gt;&gt; 65-й километр - Павлово-Посад....71 &gt;&gt;&gt; Павлово-Посад - Назарьево........73 &gt;&gt;&gt; Назарьево - Дрезна...............77 &gt;&gt;&gt; Дрезна - 85-й километр...........80<p>В некоторых случаях одну и ту же задачу можно решить разными способами, используя разные возможности регулярок. Попробуйте решить следующие задачи самостоятельно. Возможно, у вас даже получится сделать это более эффективно.</p>
131 content = 'Фрязево - 61-й километр..........64 61-й километр - 65-й километр....68 65-й километр - Павлово-Посад....71 Павлово-Посад - Назарьево........73 Назарьево - Дрезна...............77 Дрезна - 85-й километр...........80' strings = re.findall (r'\w.+?\.\d{1,3}', content) for string in strings: print (string) #Результат на экране. &gt;&gt;&gt; Фрязево - 61-й километр..........64 &gt;&gt;&gt; 61-й километр - 65-й километр....68 &gt;&gt;&gt; 65-й километр - Павлово-Посад....71 &gt;&gt;&gt; Павлово-Посад - Назарьево........73 &gt;&gt;&gt; Назарьево - Дрезна...............77 &gt;&gt;&gt; Дрезна - 85-й километр...........80<p>В некоторых случаях одну и ту же задачу можно решить разными способами, используя разные возможности регулярок. Попробуйте решить следующие задачи самостоятельно. Возможно, у вас даже получится сделать это более эффективно.</p>
132 <p>При обнародовании судебных решений из них извлекают персональные данные участников процесса - фамилии, имена и отчества. Каждое слово в Ф. И. О. начинается с заглавной буквы, при этом фамилия может быть двойная.</p>
132 <p>При обнародовании судебных решений из них извлекают персональные данные участников процесса - фамилии, имена и отчества. Каждое слово в Ф. И. О. начинается с заглавной буквы, при этом фамилия может быть двойная.</p>
133 <p>Напишите программу, которая заменит в тексте Ф. И. О. подсудимого на N.</p>
133 <p>Напишите программу, которая заменит в тексте Ф. И. О. подсудимого на N.</p>
134 <p>Подсудимая Эверт-Колокольцева Елизавета Александровна в судебном заседании вину инкриминируемого правонарушения признала в полном объёме и суду показала, что 14 сентября 1876 года, будучи в состоянии алкогольного опьянения от безысходности, в связи с состоянием здоровья позвонила со своего стационарного телефона в полицию, сообщив о том, что у неё в квартире якобы заложена бомба. После чего приехали сотрудники полиции, скорая и пожарные, которым она сообщила, что бомба - это она.</p>
134 <p>Подсудимая Эверт-Колокольцева Елизавета Александровна в судебном заседании вину инкриминируемого правонарушения признала в полном объёме и суду показала, что 14 сентября 1876 года, будучи в состоянии алкогольного опьянения от безысходности, в связи с состоянием здоровья позвонила со своего стационарного телефона в полицию, сообщив о том, что у неё в квартире якобы заложена бомба. После чего приехали сотрудники полиции, скорая и пожарные, которым она сообщила, что бомба - это она.</p>
135 <p>"Подсудимая N в судебном заседании" и далее по тексту.</p>
135 <p>"Подсудимая N в судебном заседании" и далее по тексту.</p>
136 <p><strong>Подсказка</strong></p>
136 <p><strong>Подсказка</strong></p>
137 <p>Используйте незапоминаемую опциональную группу вида (? : …)? , чтобы обозначить вторую часть фамилии после дефиса.</p>
137 <p>Используйте незапоминаемую опциональную группу вида (? : …)? , чтобы обозначить вторую часть фамилии после дефиса.</p>
138 <p><strong>Решение</strong></p>
138 <p><strong>Решение</strong></p>
139 #Сначала кладём в переменную string текст строки, по которой ведём поиск. print (re.sub (r'[А-ЯЁ]\w*' r'(?:-[А-ЯЁ]\w*)?' r'(?: [А-ЯЁ]\w*){2}', 'N', string))<p>Большинство адресов состоит из трёх частей: название улицы, номер дома и номер квартиры. Название улицы может состоять из нескольких слов, каждое из которых пишется с заглавной буквы. Номер дома может содержать после себя букву.</p>
139 #Сначала кладём в переменную string текст строки, по которой ведём поиск. print (re.sub (r'[А-ЯЁ]\w*' r'(?:-[А-ЯЁ]\w*)?' r'(?: [А-ЯЁ]\w*){2}', 'N', string))<p>Большинство адресов состоит из трёх частей: название улицы, номер дома и номер квартиры. Название улицы может состоять из нескольких слов, каждое из которых пишется с заглавной буквы. Номер дома может содержать после себя букву.</p>
140 <p>Перед названием улицы может быть написано "Улица", "улица", "Ул." или "ул.", перед номером дома - "дом" или "д.", перед номером квартиры - "квартира" или "кв.". Также номер дома и номер квартиры могут быть разделены дефисом без пробелов.</p>
140 <p>Перед названием улицы может быть написано "Улица", "улица", "Ул." или "ул.", перед номером дома - "дом" или "д.", перед номером квартиры - "квартира" или "кв.". Также номер дома и номер квартиры могут быть разделены дефисом без пробелов.</p>
141 <p>Дан текст, в нём нужно найти все адреса и вывести их в виде "Пушкина 32-135".</p>
141 <p>Дан текст, в нём нужно найти все адреса и вывести их в виде "Пушкина 32-135".</p>
142 <p>Для упрощения мы не будем учитывать дома, которые находятся не на улицах, а на площадях, набережных, бульварах и так далее.</p>
142 <p>Для упрощения мы не будем учитывать дома, которые находятся не на улицах, а на площадях, набережных, бульварах и так далее.</p>
143 <p>Добрый день!</p>
143 <p>Добрый день!</p>
144 <p>Сегодня на выезды потребуется отправить трёх-четырёх специалистов, остальных держите в офисе. Некоторые заявки пришли на конкретных людей, но можно вызвать и других, смотрите по ситуации, как лучше их отправить, чтобы всех объездить сегодня.</p>
144 <p>Сегодня на выезды потребуется отправить трёх-четырёх специалистов, остальных держите в офисе. Некоторые заявки пришли на конкретных людей, но можно вызвать и других, смотрите по ситуации, как лучше их отправить, чтобы всех объездить сегодня.</p>
145 <p>Петрову П. П. попросили выехать по адресам ул. Культуры 78 кв. 6, улица Мира дом 12Б квартира 144. Смирнова С. С. просят подъехать только по адресу: Восьмого Марта 106-19. Без предпочтений по специалистам пришли запросы с адресов: улица Свободы 54 6, Улица Шишкина дом 9 кв. 15, ул. Лермонтова 18 кв. 93.</p>
145 <p>Петрову П. П. попросили выехать по адресам ул. Культуры 78 кв. 6, улица Мира дом 12Б квартира 144. Смирнова С. С. просят подъехать только по адресу: Восьмого Марта 106-19. Без предпочтений по специалистам пришли запросы с адресов: улица Свободы 54 6, Улица Шишкина дом 9 кв. 15, ул. Лермонтова 18 кв. 93.</p>
146 <p>Все адреса скопированы из заявок, корректность подтверждена.</p>
146 <p>Все адреса скопированы из заявок, корректность подтверждена.</p>
147 <p>Культуры 78-6</p>
147 <p>Культуры 78-6</p>
148 <p>Мира 12Б-144</p>
148 <p>Мира 12Б-144</p>
149 <p>Восьмого Марта 106-19</p>
149 <p>Восьмого Марта 106-19</p>
150 <p>Свободы 54-6</p>
150 <p>Свободы 54-6</p>
151 <p>Шишкина 9-15</p>
151 <p>Шишкина 9-15</p>
152 <p>Лермонтова 18-93</p>
152 <p>Лермонтова 18-93</p>
153 <p><strong>Подсказка</strong></p>
153 <p><strong>Подсказка</strong></p>
154 <p>Используйте деление на группы, чтобы удобно выстроить структуру выражения. Попросите regex запоминать только нужные вам части адреса, чтобы функция не возвращала вам лишние подгруппы.</p>
154 <p>Используйте деление на группы, чтобы удобно выстроить структуру выражения. Попросите regex запоминать только нужные вам части адреса, чтобы функция не возвращала вам лишние подгруппы.</p>
155 <p><strong>Решение</strong></p>
155 <p><strong>Решение</strong></p>
156 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = re.compile (r'(?:[Уу]л(?:\.|ица) )?' r'((?:[А-ЯЁ]\w+)(?: [А-ЯЁ]\w+)*)' r' (?:дом |д\. )?' r'(\d+\w?)' r'[ -](?:квартира |кв\. )?' r'(\d+)') addresses = pattern.findall (text) for address in addresses: print (f'{address[0]} {address[1]}-{address[2]}')<p>Структура этого регулярного выражения довольно сложная. Чтобы в нём разобраться, посмотрите на схему. Прямоугольники обозначают обязательные элементы, овалы - опциональные. Развилки символизируют разные варианты, которые допускает наш шаблон. Красным цветом очерчены группы, которые мы запоминаем.</p>
156 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = re.compile (r'(?:[Уу]л(?:\.|ица) )?' r'((?:[А-ЯЁ]\w+)(?: [А-ЯЁ]\w+)*)' r' (?:дом |д\. )?' r'(\d+\w?)' r'[ -](?:квартира |кв\. )?' r'(\d+)') addresses = pattern.findall (text) for address in addresses: print (f'{address[0]} {address[1]}-{address[2]}')<p>Структура этого регулярного выражения довольно сложная. Чтобы в нём разобраться, посмотрите на схему. Прямоугольники обозначают обязательные элементы, овалы - опциональные. Развилки символизируют разные варианты, которые допускает наш шаблон. Красным цветом очерчены группы, которые мы запоминаем.</p>
157 <em>Инфографика: Майя Мальгина для Skillbox Media</em><p>Писатели в поиске собственного неповторимого стиля нередко изобретают оригинальные творческие приёмы и неукоснительно им следуют. Например, Сергей Довлатов следил за тем, чтобы слова в предложении не начинались с одной и той же буквы.</p>
157 <em>Инфографика: Майя Мальгина для Skillbox Media</em><p>Писатели в поиске собственного неповторимого стиля нередко изобретают оригинальные творческие приёмы и неукоснительно им следуют. Например, Сергей Довлатов следил за тем, чтобы слова в предложении не начинались с одной и той же буквы.</p>
158 <p>Даны несколько предложений. Программа должна проверить, встречаются ли в каждом из них слова на одинаковую букву. Если таких нет, она печатает: "Метод Довлатова соблюдён". А если есть: "Вы расстроили Сергея Донатовича".</p>
158 <p>Даны несколько предложений. Программа должна проверить, встречаются ли в каждом из них слова на одинаковую букву. Если таких нет, она печатает: "Метод Довлатова соблюдён". А если есть: "Вы расстроили Сергея Донатовича".</p>
159 <p><strong>Важно.</strong>Чтобы регулярные выражения не рассматривали заглавные и прописные буквы как разные символы, передайте re-функции дополнительный аргумент flags=re.I или flags=re.IGNORECASE.</p>
159 <p><strong>Важно.</strong>Чтобы регулярные выражения не рассматривали заглавные и прописные буквы как разные символы, передайте re-функции дополнительный аргумент flags=re.I или flags=re.IGNORECASE.</p>
160 <p>Здесь все слова начинаются с разных букв.</p>
160 <p>Здесь все слова начинаются с разных букв.</p>
161 <p>А в этом предложении есть слова, которые всё-таки начинаются на одну и ту же букву.</p>
161 <p>А в этом предложении есть слова, которые всё-таки начинаются на одну и ту же букву.</p>
162 <p>А здесь совсем интересно: символ "а" однобуквенный.</p>
162 <p>А здесь совсем интересно: символ "а" однобуквенный.</p>
163 <p>Метод Довлатова соблюдён</p>
163 <p>Метод Довлатова соблюдён</p>
164 <p>Вы расстроили Сергея Донатовича</p>
164 <p>Вы расстроили Сергея Донатовича</p>
165 <p>Вы расстроили Сергея Донатовича</p>
165 <p>Вы расстроили Сергея Донатовича</p>
166 <p><strong>Подсказка</strong></p>
166 <p><strong>Подсказка</strong></p>
167 <p>Чтобы указать на начало слова, используйте символ \b.</p>
167 <p>Чтобы указать на начало слова, используйте символ \b.</p>
168 <p>Чтобы в каждом совпадении regex не старалось захватить максимум, используйте ленивый пропуск.</p>
168 <p>Чтобы в каждом совпадении regex не старалось захватить максимум, используйте ленивый пропуск.</p>
169 <p>Чтобы найти повторяющийся символ, используйте ссылку на группу в виде \1.</p>
169 <p>Чтобы найти повторяющийся символ, используйте ссылку на группу в виде \1.</p>
170 <p><strong>Решение</strong></p>
170 <p><strong>Решение</strong></p>
171 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = r'\b(\w)\w*.*?\b\1' match = re.search (pattern, string, flags=re.I) if match is None: print ('Метод Довлатова соблюдён') else: print ('Вы расстроили Сергея Донатовича')<p>Вернёмся к регулярному выражению, которое ищет даты в учебнике истории: (? :\d{4})|(? : [IVX]+ век).</p>
171 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = r'\b(\w)\w*.*?\b\1' match = re.search (pattern, string, flags=re.I) if match is None: print ('Метод Довлатова соблюдён') else: print ('Вы расстроили Сергея Донатовича')<p>Вернёмся к регулярному выражению, которое ищет даты в учебнике истории: (? :\d{4})|(? : [IVX]+ век).</p>
172 <p>Оно в целом справляется со своей задачей, но также находит много ненужных чисел. Например, количество человек, которые участвовали в битве, тоже может быть описано четырьмя цифрами подряд.</p>
172 <p>Оно в целом справляется со своей задачей, но также находит много ненужных чисел. Например, количество человек, которые участвовали в битве, тоже может быть описано четырьмя цифрами подряд.</p>
173 <p>Чтобы не получать лишние результаты, обратим внимание на то, как именно могут быть записаны годы. Есть несколько вариантов записи: 1400 год, 1400 г., 1400-1500 годы, 1400-1500 гг., (1400), (1400-1500).</p>
173 <p>Чтобы не получать лишние результаты, обратим внимание на то, как именно могут быть записаны годы. Есть несколько вариантов записи: 1400 год, 1400 г., 1400-1500 годы, 1400-1500 гг., (1400), (1400-1500).</p>
174 <p>Чтобы немного упростить задачу и не раздувать регулярное выражение, мы не будем искать конструкции "с такого-то по такой-то год" и "между таким-то и таким-то годом".</p>
174 <p>Чтобы немного упростить задачу и не раздувать регулярное выражение, мы не будем искать конструкции "с такого-то по такой-то год" и "между таким-то и таким-то годом".</p>
175 <p><strong>Важное замечание.</strong>Не забывайте про экранирование, если хотите использовать точки и скобки в качестве обычных, а не специальных символов. Так программа правильно поймёт, что вы имеете в виду.</p>
175 <p><strong>Важное замечание.</strong>Не забывайте про экранирование, если хотите использовать точки и скобки в качестве обычных, а не специальных символов. Так программа правильно поймёт, что вы имеете в виду.</p>
176 <p>Началом Реформации принято считать 31 октября 1517 г. - день, когда Мартин Лютер (1483-1546) прибил к дверям виттенбергской Замковой церкви свои "95 тезисов", в которых выступил против злоупотреблений Католической церкви. Реформация охватила практически всю Европу и продолжалась в течение всего XVI века и первой половины XVII века. Одно из самых известных и кровавых событий Реформации - Варфоломеевская ночь во Франции, произошедшая в ночь на 24 августа 1572 года.</p>
176 <p>Началом Реформации принято считать 31 октября 1517 г. - день, когда Мартин Лютер (1483-1546) прибил к дверям виттенбергской Замковой церкви свои "95 тезисов", в которых выступил против злоупотреблений Католической церкви. Реформация охватила практически всю Европу и продолжалась в течение всего XVI века и первой половины XVII века. Одно из самых известных и кровавых событий Реформации - Варфоломеевская ночь во Франции, произошедшая в ночь на 24 августа 1572 года.</p>
177 <p>Точное число жертв так и не удалось установить достоверно. Погибли по меньшей мере 2000 гугенотов в Париже и 3000 - в провинциях. Герцог де Сюлли, сам едва избежавший смерти во время резни, говорил о 70 000 жертв. Для Парижа единственным точным числом остаётся 1100 погибших во время Варфоломеевской ночи.</p>
177 <p>Точное число жертв так и не удалось установить достоверно. Погибли по меньшей мере 2000 гугенотов в Париже и 3000 - в провинциях. Герцог де Сюлли, сам едва избежавший смерти во время резни, говорил о 70 000 жертв. Для Парижа единственным точным числом остаётся 1100 погибших во время Варфоломеевской ночи.</p>
178 <p>Этому событию предшествовали три других, произошедшие в 1570-1572 годах: Сен-Жерменский мирный договор (1570), свадьба гугенота Генриха Наваррского и Маргариты Валуа (1572) и неудавшееся покушение на убийство адмирала Колиньи (1572).</p>
178 <p>Этому событию предшествовали три других, произошедшие в 1570-1572 годах: Сен-Жерменский мирный договор (1570), свадьба гугенота Генриха Наваррского и Маргариты Валуа (1572) и неудавшееся покушение на убийство адмирала Колиньи (1572).</p>
179 <p>['1517 г.', '(1483-1546)', 'XVI век', 'XVII век', '1572 год', '1570-1572 годах', '(1570)', '(1572)', '(1572)']</p>
179 <p>['1517 г.', '(1483-1546)', 'XVI век', 'XVII век', '1572 год', '1570-1572 годах', '(1570)', '(1572)', '(1572)']</p>
180 <p><strong>Решение</strong></p>
180 <p><strong>Решение</strong></p>
181 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = re.compile (r'(?:\(\d{4}(?:-\d{4})?\))' r'|' r'(?:' r'(?:\d{4}-)?\d{4} ' r'(?:' r'(?:год(?:ы|ах|ов)?)' r'|' r'(?:гг?\.)' r')' r')' r'|' r'(?:[IVX]+ век)') print (pattern.findall (string))<p>Если вам сложно разобраться в структуре этого выражения, то вот его схема:</p>
181 #Сначала кладём в переменную string текст строки, по которой ведём поиск. pattern = re.compile (r'(?:\(\d{4}(?:-\d{4})?\))' r'|' r'(?:' r'(?:\d{4}-)?\d{4} ' r'(?:' r'(?:год(?:ы|ах|ов)?)' r'|' r'(?:гг?\.)' r')' r')' r'|' r'(?:[IVX]+ век)') print (pattern.findall (string))<p>Если вам сложно разобраться в структуре этого выражения, то вот его схема:</p>
182 <em>Инфографика: Майя Мальгина для Skillbox Media</em><p>Python для всех</p>
182 <em>Инфографика: Майя Мальгина для Skillbox Media</em><p>Python для всех</p>
183 <p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
183 <p>Вы освоите Python на практике и создадите проекты для портфолио - телеграм-бот, веб-парсер и сайт с нуля. А ещё получите готовый план выхода на удалёнку и фриланс. Спикер - руководитель отдела разработки в "Сбере".</p>
184 <p><a>Пройти бесплатно</a></p>
184 <p><a>Пройти бесплатно</a></p>
185 <a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>
185 <a><b>Бесплатный курс по разработке на Python ➞</b>Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер - руководитель отдела разработки в "Сбере". Пройти курс</a>