2.36. 그룹과 앵커¶
패턴은 “이 문자열이 일치한다”라고 말하는 것 이상의 일을 할 수 있습니다. 즉, 일치한 부분들을 분리해서 각각을 이름으로 애플리케이션에 전달할 수 있습니다. 패턴의 일부를 괄호로 묶으면 그것이 캡처 그룹(capturing group)이 됩니다. 그러면 match 객체가 각 그룹을 별도의 부분 문자열로 노출합니다.
2.36.1. 캡처 그룹¶
패턴의 어느 부분이든 (...) 로 감싸면 그것이 일치한 내용을 캡처합니다:
>>> 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'
그룹 0은 항상 전체 일치 결과입니다.
그룹 1, 2, … 은 캡처된 부분 문자열로, 여는 괄호 기준으로 왼쪽에서 오른쪽으로 번호가 매겨집니다.
마지막 그룹을 넘어서는 인덱스로
match.group()을 호출하면IndexError가 발생합니다.
흔히 쓰이는 패턴은 “알려진 구조를 일치시키고, 가변적인 부분들을 정수로 캡처하기” 입니다:
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. 비캡처 그룹¶
괄호는 또한 하위 표현식을 그룹화하여 수량자가 그룹 전체에 적용되도록 합니다. r'(ab)+' 에서 그룹화의 유일한 목적이 바로 이것입니다. 즉 “ab 가 하나 이상”이라는 뜻입니다. ab 가 그룹 1로 나타나는 것은 부수적인 효과입니다.
캡처하지 않고 그룹화하려면 (?:...) 를 사용합니다:
>>> re.search(r'(?:ab)+', 'xababy').group(0)
'abab'
비캡처 그룹은 패턴이 구조를 위해 그룹화를 사용하지만 각 부분을 따로 꺼낼 필요는 없을 때 그룹 번호를 깔끔하게 유지해 줍니다.
2.36.3. 앵커¶
앵커는 문자를 일치시키지 않습니다. 앵커는 위치를 일치시킵니다.
^– 문자열의 시작.$– 문자열의 끝.
앵커는 re.match() 와 re.search() 가 다르게 동작하게 만드는 요소입니다. re.match(p, s) 는 re.search('^' + p, s) 와 동일합니다. 즉 패턴이 위치 0에서 시작하도록 강제합니다. 패턴 끝에 $ 를 추가하면 패턴이 다른 것이 아닌 전체 문자열과 일치하도록 만듭니다:
>>> re.search(r'^\d+$', '12345')
<match num=1>
>>> re.search(r'^\d+$', '12345 ok') is None
True
MicroPython re 에서 ^ 와 $ 는 항상 re.search() 에 전달된 전체 문자열의 시작과 끝을 의미합니다. 이들이 내장된 모든 개행 위치에서 일치하도록 만드는 re.MULTILINE 플래그는 없으며, $ 는 뒤따르는 \n 앞 위치와도 일치하지 않습니다. 이것은 입력의 절대적인 끝이어야 합니다. 줄 단위 동작을 얻으려면 먼저 입력을 개행으로 분할한 다음 각 줄에 대해 패턴을 실행하세요.
2.36.4. 문자 집합¶
대괄호는 명시적인 문자 집합을 정의합니다. 일치는 그 집합에서 정확히 한 문자를 소비합니다.
[abc]–a,b,c중 하나.[a-z]–a-z범위(양 끝 포함)의 한 문자.[a-zA-Z0-9]– 문자 또는 숫자. 세 개의 범위를 결합한 것입니다.[^abc]–a,b,c중 하나가 아닌 문자.^는 대괄호 안의 첫 번째 문자일 때만 부정의 의미를 갖습니다.
예시:
>>> 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
첫 번째 호출은 리터럴 텍스트가 소문자이기 때문에 실제로 None 을 반환합니다. MicroPython re 에는 re.IGNORECASE 플래그가 없습니다. 대소문자를 구분하지 않고 일치시키려면 두 경우를 모두 집합에 작성하세요:
>>> re.search(r'[A-Fa-f0-9]{6}', 'colour #1a2b3c rest').group(0)
'1a2b3c'
클래스 단축 표현(\d, \s, \w 및 그 부정형)은 [...] 안에서도 사용할 수 있습니다. [\w-] 는 “단어 문자 또는 리터럴 대시”를 의미합니다.
2.36.5. 탐욕적 수량자 vs 게으른 수량자¶
수량자 *, +, ?, {m,n} 은 기본적으로 탐욕적(greedy)입니다. 즉, 나머지 패턴이 여전히 허용하는 한 최대한 많은 문자를 일치시킵니다. 종종 그것이 바로 원하는 동작이지만, 그렇지 않은 경우도 있습니다:
>>> re.search(r'<(.+)>', 'a <b> <c> d').group(1)
'b> <c'
탐욕적인 .+ 는 마지막 > 까지 모두 가져갔습니다. ? 를 덧붙이면 수량자가 게으르게(lazy) 됩니다. 즉 가능한 한 적게 일치시킵니다:
>>> re.search(r'<(.+?)>', 'a <b> <c> d').group(1)
'b'
게으른 형태는 첫 번째 > 에서 멈춥니다. 게으른 수량자는 문자열에서 짝을 이룬 구분 기호를 추출할 때 끊임없이 등장합니다.
2.36.6. 치환에서의 역참조¶
re.sub() 는 치환 문자열에서 \1, \2, … 를 통해 캡처된 그룹을 다시 참조할 수 있습니다. 치환은 캡처된 부분들을 사용하여 모든 일치 결과를 다시 작성합니다:
>>> re.sub(r'(\d+)\.(\d+)', r'\2.\1', 'swap 12.34 and 5.6')
'swap 34.12 and 6.5'
각 일치는 두 개의 숫자를 캡처하고, 치환은 그것들을 서로 바꿉니다. \g<1> 은 같은 것을 위한 대체 구문입니다. 치환에서 다음 문자가 숫자일 때 유용합니다(“그룹 10”으로 읽히는 대신 그룹 1에 리터럴 0을 덧붙이려면 r'\g<1>0').
2.36.7. 사용할 수 없는 것¶
CPython의 패턴이 여기로 들어와 당신을 놀라게 할 경우를 대비해, MicroPython re 가 지원하지 않는 것들을 다시 짚어봅니다:
전방 탐색
(?=...)과 후방 탐색(?<=...)– 구현되지 않았습니다.이름 있는 그룹
(?P<name>...)과 이름 있는 역참조(?P=name)– 구현되지 않았습니다.re.IGNORECASE,re.MULTILINE,re.DOTALL같은 플래그 상수 – 적용되지 않습니다. 대소문자를 구분하지 않는 집합을 만들거나 입력을 직접 미리 분할하세요.match.groups(),match.span(),match.start(),match.end()메서드는 출시된 어떤 OpenMV 보드도 활성화하지 않는 ROM 레벨에서 막혀 있습니다. 이들에 의존하는 코드는 카메라에서 실행되지 않습니다.
패턴, 그룹, 앵커를 갖추면, 카메라의 정규식 도구 모음은 한자리에서 익힐 수 있을 만큼 작으면서도 문맥 의존적 파싱을 제외한 거의 모든 것을 할 수 있을 만큼 풍부합니다.