2.36. Grupy i kotwice

Wzorzec może zrobić więcej niż tylko stwierdzić „ten łańcuch pasuje” – potrafi rozłożyć dopasowane fragmenty i przekazać każdy z nich aplikacji według nazwy. Nawiasy wokół części wzorca tworzą grupę przechwytującą; obiekt dopasowania udostępnia wtedy każdą grupę jako osobny podłańcuch.

2.36.1. Grupy przechwytujące

Umieść dowolną część wzorca w (...), aby przechwycić to, co dopasowała:

>>> import re
>>> m = re.search(r'temp (\d+) at (\d+)s', 'temp 42 at 137s ok')
>>> m.group(0)
'temp 42 at 137s'
>>> m.group(1)
'42'
>>> m.group(2)
'137'
  • Grupa 0 to zawsze całe dopasowanie.

  • Grupy 1, 2, … to przechwycone podłańcuchy, numerowane od lewej do prawej według otwierającego je nawiasu.

  • Wywołanie match.group() z indeksem większym niż numer ostatniej grupy zgłasza IndexError.

Częstym wzorcem jest „dopasuj znaną strukturę, przechwyć zmienne części jako liczby całkowite”:

def parse_temp(line):
    m = re.search(r'temp (\d+) at (\d+)s', line)
    if not m:
        return None
    return int(m.group(1)), int(m.group(2))

2.36.2. Grupy nieprzechwytujące

Nawiasy grupują także podwyrażenie, dzięki czemu kwantyfikator może zostać zastosowany do całej grupy. To jedyny cel grupowania w r'(ab)+' – „jedno lub więcej wystąpień ab„. Fakt, że ab pojawia się jako grupa 1, jest efektem ubocznym.

Aby zgrupować bez przechwytywania, użyj (?:...)

>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'

Grupy nieprzechwytujące utrzymują porządek w numeracji grup, gdy wzorzec wykorzystuje grupowanie do struktury, ale nie zależy mu na wyciąganiu poszczególnych fragmentów.

2.36.3. Kotwice

Kotwice nie dopasowują znaku – dopasowują pozycję.

  • ^ – początek łańcucha.

  • $ – koniec łańcucha.

To właśnie kotwice sprawiają, że re.match() i re.search() zachowują się różnie. re.match(p, s) jest tym samym co re.search('^' + p, s): wymusza rozpoczęcie wzorca na pozycji 0. Dodanie $ na końcu wzorca sprawia, że dopasowuje on cały łańcuch i nic poza nim:

>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True

^ i $ w re w MicroPython zawsze oznaczają początek i koniec całego łańcucha przekazanego do re.search(). Nie ma flagi re.MULTILINE, która sprawiłaby, że dopasowują się one przy każdym osadzonym znaku nowej linii, a $ nie dopasowuje też pozycji przed końcowym \n – musi to być absolutny koniec wejścia. Aby uzyskać zachowanie wierszowe, najpierw podziel wejście na znakach nowej linii i uruchom wzorzec na każdym wierszu.

2.36.4. Zbiory znaków

Nawiasy kwadratowe definiują jawny zbiór znaków. Dopasowanie pochłania dokładnie jeden znak ze zbioru.

  • [abc] – jeden z a, b, c.

  • [a-z] – jeden znak z zakresu a-z (włącznie).

  • [a-zA-Z0-9] – litery lub cyfry. Trzy połączone zakresy.

  • [^abc]nie jeden z a, b, c. ^ neguje tylko wtedy, gdy jest pierwszym znakiem wewnątrz nawiasów.

Przykłady:

>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest').group(0)
'1A2B3C'
>>> re.search(r'[A-F0-9]{6}', 'colour #1a2b3c rest') is None
True

Pierwsze wywołanie w praktyce zwraca None, ponieważ tekst literalny jest pisany małymi literami. re w MicroPython nie ma flagi re.IGNORECASE – aby dopasowywać bez rozróżniania wielkości liter, wpisz oba warianty do zbioru:

>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'

Skróty klas znaków (\d, \s, \w oraz ich zanegowane formy) mogą być używane także wewnątrz [...]: [\w-] oznacza „znaki wyrazowe lub literalny myślnik”.

2.36.5. Kwantyfikatory zachłanne a leniwe

Kwantyfikatory *, +, ? oraz {m,n} są domyślnie zachłanne – dopasowują tyle znaków, na ile pozwoli jeszcze reszta wzorca. Często jest to dokładnie to, czego oczekujemy; czasami jednak nie:

>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'

Zachłanne .+ zagarnęło wszystko aż do ostatniego >. Dopisanie ? czyni kwantyfikator leniwym – dopasowuje on tak mało, jak to możliwe:

>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'

Forma leniwa zatrzymuje się przy pierwszym >. Leniwe kwantyfikatory pojawiają się stale przy wyciąganiu zrównoważonych ograniczników z łańcucha.

2.36.6. Odwołania wsteczne w podstawianiu

re.sub() może odwoływać się do przechwyconych grup w łańcuchu zastępującym za pomocą \1, \2, … Podstawianie przepisuje każde dopasowanie z użyciem przechwyconych fragmentów:

>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'

Każde dopasowanie przechwytuje dwie liczby, a zamiana je przestawia. \g<1> to alternatywna składnia tego samego – przydatna, gdy następny znak w zastąpieniu jest cyfrą (r'\g<1>0' dopisuje literalne zero do grupy 1, zamiast odczytywać „grupę 10”).

2.36.7. Czego nie ma

Przypomnienie tego, czego re w MicroPython nie obsługuje, na wypadek gdyby wylądował tu wzorzec z CPython i Cię zaskoczył:

  • Wyszukiwanie w przód (?=...) i wstecz (?<=...) – niezaimplementowane.

  • Grupy nazwane (?P<name>...) i nazwane odwołania wsteczne (?P=name) – niezaimplementowane.

  • Stałe flag, takie jak re.IGNORECASE, re.MULTILINE, re.DOTALL – nieuwzględniane. Zbuduj samodzielnie zbiór niezależny od wielkości liter lub wstępnie podziel wejście.

  • Metody match.groups(), match.span(), match.start() i match.end() są dostępne dopiero na poziomie ROM, którego nie włącza żadna dostarczana płytka OpenMV. Kod, który na nich polega, nie uruchomi się na kamerze.

Dzięki wzorcom, grupom i kotwicom zestaw narzędzi wyrażeń regularnych na kamerze jest na tyle mały, by nauczyć się go za jednym posiedzeniem, i na tyle bogaty, by zrobić wszystko z wyjątkiem parsowania zależnego od kontekstu.