2.35. 패턴 기초¶
정규 표현식은 문자열을 정확한 내용이 아니라 형태로 기술하기 위한 작은 언어입니다. 어떤 문자열이 당신이 기술할 수는 있지만 열거할 수는 없는 패턴을 따를 때에만 일치한다고 할 때 사용하세요. 예를 들어 “숫자 시퀀스 뒤에 단위가 오는 것”, “ERROR 로 시작하고 숫자로 끝나는 줄”, “선택적인 v 접두사가 붙은, 이 파일 확장자들 중 어느 것이든 임의의 순서로” 같은 경우입니다.
일반 문자열 메서드로 처리할 수 없을 때에 한해서만 re 를 사용하세요.
str.startswith(),str.endswith()– 고정된 접두사 또는 접미사 검사.in– 고정된 부분 문자열이 존재하는지 검사.str.split(),str.find(),str.replace()– 고정된 구분 기호 다루기.
이들 각각은 동등한 정규식보다 더 빠르고, 읽기 쉬우며, 잘못 작성하기 어렵습니다. 문자열의 형태가 중요하고 정확한 부분 문자열은 중요하지 않을 때 정규식을 사용하세요.
2.35.1. 당신이 사용하게 될 네 가지¶
MicroPython re 모듈은 네 가지를 노출합니다:
re.compile()– 패턴 문자열을 재사용할 수 있는 컴파일된 패턴 객체로 변환합니다.re.match()– 문자열의 시작에서 패턴을 시도합니다. 패턴은 위치 0에 고정됩니다.re.search()– 문자열의 어디에서나 패턴을 시도합니다. 첫 번째 일치를 반환합니다.re.sub()– 모든 일치를 찾아 치환합니다.
CPython 대비 주목할 만한 누락 사항: 모듈 레벨에는 re.findall 도, re.finditer 도, re.split 도 없으며(컴파일된 패턴에는 split 메서드가 대신 있습니다), re.fullmatch 도 없고, re.IGNORECASE 같은 플래그 상수도 없습니다. CPython에서 그런 것들 중 하나를 사용하게 될 상황에서는 루프 안의 re.search() 로 동등한 기능을 만드세요.
2.35.2. 첫 번째 패턴¶
패턴 r'\d+' 은 하나 이상의 숫자와 일치합니다:
>>> import re
>>> m = re.search(r'\d+', 'sensor reading 42 ok')
>>> m.group(0)
'42'
몇 가지 주목할 점:
패턴은 raw string (
r'...')으로 작성하므로\d의 백슬래시가 Python 문자열 이스케이프로 처리되지 않고re에 전달됩니다. 정규식 패턴에는 항상 raw string을 사용하세요.re.search()는 성공 시 match 객체를, 실패 시None을 반환합니다.match.group()을 호출하기 전에 항상 확인하세요.m.group(0)은 패턴이 일치시킨 전체 텍스트입니다. 패턴에 캡처 괄호가 포함되면 그룹 1, 2, … 가 나중에 나타납니다.
동일한 패턴을 re.match() 와 함께 사용하면 문자열이 숫자로 시작하지 않으므로 None 을 반환합니다:
>>> re.match(r'\d+', 'sensor reading 42 ok') is None
True
>>> re.match(r'\d+', '42 readings')
<match num=1>
2.35.3. 패턴의 구성 요소¶
대부분의 유용한 패턴은 작은 구성 요소 집합으로 만들어집니다. MicroPython에서 동작하는 것들:
리터럴 문자 – 특수하지 않은 문자는 자기 자신과 일치합니다. hello 는 hello 와 일치합니다.
특수 문자 – . ^ $ * + ? { } [ ] \ | ( ) 는 모두 아래에 설명된 의미를 가집니다. 그중 하나를 리터럴로 일치시키려면 백슬래시로 이스케이프하세요. \. 는 리터럴 점과 일치합니다.
문자 클래스 – 흔한 문자 집합을 위한 단축 표현:
\d– 임의의 숫자0-9\D– 숫자가 아닌 임의의 문자\s– 임의의 공백 문자(스페이스, 탭, 개행)\S– 공백이 아닌 임의의 문자\w– 임의의 “단어” 문자: 문자, 숫자, 밑줄\W– 단어가 아닌 임의의 문자.– 개행을 제외한 임의의 문자
수량자 – 앞의 구성 요소가 몇 번 일치해야 하는지:
*– 0개 이상(탐욕적)+– 하나 이상(탐욕적)?– 0개 또는 1개{n}– 정확히 n개{m,n}– m개에서 n개 사이(양 끝 포함)
결합: \d{3}-\d{4} 는 숫자 세 개, 대시, 숫자 네 개와 일치합니다. \s+ 는 하나 이상의 공백 문자와 일치합니다. hello.*world 는 hello, 그 다음 무엇이든(아무것도 없는 것 포함), 그 다음 world 와 일치합니다.
참고
탐욕적(greedy)이라는 것은 수량자가 나머지 패턴이 여전히 일치할 수 있게 하면서도 입력을 최대한 많이 소비한다는 뜻입니다. hello x world y world 에 대해, hello.*world 의 .* 는 끝에 world 를 여전히 남기는 가장 긴 구간과 일치합니다. 즉 더 짧은 x 가 아니라 x world y 를 캡처합니다. + 와 {m,n} 범위 형태도 마찬가지입니다. 엔진은 가능한 한 가장 긴 일치를 취하고, 나머지 패턴이 실패할 때에만 물러섭니다.
2.35.4. 치환¶
re.sub() 는 모든 일치를 찾아 문자열로 치환합니다. 치환은 \1, \2, … 를 통해 캡처된 그룹을 참조할 수 있습니다(나머지 그룹 구문과 함께 나중에 다룹니다). 그룹이 없으면 re.sub 는 정규식에 대한 단순한 찾기-바꾸기입니다:
>>> re.sub(r'\s+', ' ', 'too many spaces')
'too many spaces'
>>> re.sub(r'\d+', 'N', 'log 12, log 345, log 6')
'log N, log N, log N'
세 번째 인수는 작업 대상이 되는 문자열입니다. 결과는 모든 일치가 치환된 새 문자열입니다.
2.35.5. 분할 – 컴파일된 패턴에서만¶
모듈 레벨에는 re.split 이 없습니다. 정규식으로 분할하려면 먼저 패턴을 컴파일하고 그 split 메서드를 호출하세요:
>>> sep = re.compile(r'\s*,\s*')
>>> sep.split('a , b,c , d')
['a', 'b', 'c', 'd']
선택적인 두 번째 인수는 분할 횟수를 제한합니다:
>>> sep.split('a, b, c, d', 2)
['a', 'b', 'c, d']
2.35.6. 재사용을 위한 컴파일¶
동일한 패턴이 여러 번 실행된다면(루프 안이나 자주 호출되는 함수 안에서), 한 번 컴파일하고 컴파일된 객체를 재사용하세요:
digit_run = re.compile(r'\d+')
def first_number(line):
m = digit_run.search(line)
return int(m.group(0)) if m else None
컴파일된 객체에서 pattern.match() 와 pattern.search() 를 호출하는 것은 모듈 레벨 함수와 동일하지만, 호출할 때마다 다시 컴파일하는 비용을 건너뜁니다.
2.35.7. 아무것도 일치시키지 않는 패턴¶
특히 세 가지 패턴이 개발자들을 곤란하게 합니다:
.*는 빈 문자열과 일치합니다.re.search(r'.*', s).group(0)은 어떤 입력에 대해서도''를 반환합니다.이스케이프되지 않은 특수 문자가 있는 패턴은 구문 오류입니다.
re.compile(r'cost: $5')는$가 “문자열의 끝”을 의미하기 때문에ValueError를 발생시킵니다.r'cost: \$5'를 사용하세요.점
.은 개행과 일치하지 않습니다. 개행을 넘어 일치시키려면[\s\S]로 명시적으로 개행을 처리하도록 패턴을 작성하거나 한 번에 한 줄씩 입력하세요.
이러한 구성 요소들로 패턴은 거의 모든 고정된 형태의 텍스트 조각과 일치할 수 있습니다. 일치 결과에서 구조화된 데이터를 다시 꺼내려면 캡처 그룹이 필요합니다.