Группировка выражений

Группировка выражений указывает, что последовательность символов надо рассматривать как одно целое. Однако это не единственное преимущество группировки.

Кроме этого, с помощью групп можно получать только определенную часть строки, которая была описана выражением. Это очень полезно в ситуациях, когда надо описать строку достаточно подробно, чтобы отобрать нужные строки, но в то же время из самой строки надо получить только определенное значение.

Например, из log-файла надо отобрать строки, в которых встречается «%SW_MATM-4-MACFLAP_NOTIF», а затем из каждой такой строки получить MAC-адрес, VLAN и интерфейсы. В этом случае регулярное выражение просто должно описывать строку, а все части строки, которые надо получить в результате, просто заключаются в скобки.

В Python есть два варианта использования групп:

  • Нумерованные группы
  • Именованные группы

Нумерованные группы

Группа определяется помещением выражения в круглые скобки ().

Внутри выражения группы нумеруются слева направо, начиная с 1. Затем к группам можно обращаться по номерам и получать текст, который соответствует выражению в группе.

Пример использования групп:

In [8]: line = "FastEthernet0/1            10.0.12.1       YES manual up                    up"
In [9]: match = re.search(r'(\S+)\s+([\w.]+)\s+.*', line)

В данном примере указаны две группы:

  • первая группа - любые символы, кроме пробельных
  • вторая группа - любая буква или цифра (символ \w) или точка

Вторую группу можно было описать так же, как и первую. Другой вариант сделан просто для примера

Теперь можно обращаться к группам по номеру. Группа 0 - это строка, которая соответствует всему шаблону:

In [10]: match.group(0)
Out[10]: 'FastEthernet0/1            10.0.12.1       YES manual up                    up'

In [11]: match.group(1)
Out[11]: 'FastEthernet0/1'

In [12]: match.group(2)
Out[12]: '10.0.12.1'

При необходимости можно перечислить несколько номеров групп:

In [13]: match.group(1, 2)
Out[13]: ('FastEthernet0/1', '10.0.12.1')

In [14]: match.group(2, 1, 2)
Out[14]: ('10.0.12.1', 'FastEthernet0/1', '10.0.12.1')

Начиная с версии Python 3.6, к группам можно обращаться таким образом:

In [15]: match[0]
Out[15]: 'FastEthernet0/1            10.0.12.1       YES manual up                    up'

In [16]: match[1]
Out[16]: 'FastEthernet0/1'

In [17]: match[2]
Out[17]: '10.0.12.1'

Для вывода всех подстрок, которые соответствуют указанным группам, используется метод groups:

In [18]: match.groups()
Out[18]: ('FastEthernet0/1', '10.0.12.1')

Именованные группы

Когда выражение сложное, не очень удобно определять номер группы. Плюс, при дополнении выражения, может получиться так, что порядок групп изменился, и придется изменить и код, который ссылается на группы.

Именованные группы позволяют задавать группе имя.

Синтаксис именованной группы (?P<name>regex):

In [19]: line = "FastEthernet0/1            10.0.12.1       YES manual up                    up"

In [20]: match = re.search(r'(?P<intf>\S+)\s+(?P<address>[\d.]+)\s+', line)

Теперь к этим группам можно обращаться по имени:

In [21]: match.group('intf')
Out[21]: 'FastEthernet0/1'

In [22]: match.group('address')
Out[22]: '10.0.12.1'

Также очень полезно то, что с помощью метода groupdict(), можно получить словарь, где ключи - имена групп, а значения - подстроки, которые им соответствуют:

In [23]: match.groupdict()
Out[23]: {'address': '10.0.12.1', 'intf': 'FastEthernet0/1'}

И в таком случае можно добавить группы в регулярное выражение и полагаться на их имя, а не на порядок:

In [24]: match = re.search(r'(?P<intf>\S+)\s+(?P<address>[\d\.]+)\s+\w+\s+\w+\s+(?P<status>up|down)\s+(?P<protocol>up|down)', line)

In [25]: match.groupdict()
Out[25]:
{'address': '10.0.12.1',
 'intf': 'FastEthernet0/1',
 'protocol': 'up',
 'status': 'up'}