이 글은 도서 '점프 투 파이썬' 연습문제를 토대로 작성하였습니다.
+, *, [], {}
: 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 변경된다.(소비된다라고 표현)|, ^, $, \A, \Z, \b, \B
: 매치가 진행될 때 현재 매치되고 있는 문자열의 위치를 소비하지않는다.(zero-width assertions)|
: or과 동일한 의미로 사용된다. A|B라는 정규식이 있다면 A 또는 B라는 의미가 된다.
>>> p = re.compile('Crow|Servo')
>>> m = p.match('CrowHello')
>>> print(m)
<re.Match object: span=(0,4), match='Crow'>
^
: 문자열의 맨 처음과 일치함을 의미.
>>>print(re.search('^Life', 'Life is too short')
<re.Match object; span=(0, 4) match='Life'>
>>>print(re.search('^Life', 'My Life'))
None
$
: ^
메타문자와 반대의 경우이다. 즉 $
는 문자열의 끝과 매치함을 의미한다.
>>> print(re.search('short$', 'Life is too short')
<re.Match object; span=(12, 17) match='short'>
>>> print(re.search('short$', 'Life is too short, you need python'))
None
\A
: 문자열의 처음과 매치됨을 의미한다. ^
와 동일한 의미지만 re.MULTILINE
을 사용할 때 다르게 해석된다. re.MULTILINE
을 사용할 경우 ^
는 각 줄의 문자열의 처음과 매치되지만 \A
는 줄과 상관없이 전체 문자열의 처음하고만 매치된다.
\Z
: 문자열의 끝과 매치됨을 의미한다. $
와 같은 의미지만 re.MULTILINE
옵션을 사용할 때 $
와 달리 전체 문자열의 끝과 매치된다.
\b
: 단어 구분자(Word boundary)이다. 보통 단어는 whitespace에 의해 구분된다.
다음 예를 살펴보자.
>>> p = re.compile(r'\bclass\b')
>>> print(p.search('no class at all'))
<re.Match object; span=(3, 8) match='class'>
'\bclass\b'는 앞뒤가 whitespace로 구분된 class라는 단어와 매치됨을 의미한다.
>>> p = re.compile(r'\bclass\b')
>>> print(p.search('the declassified algorithm'))
None
class 문자열이 있긴하지만 whitespace로 구분된 단어가 아니기에 None
이 출력되는 것을 볼 수 있다.
\B
: \b
와 반대의 경우, 즉 whitespace로 구분된 경우가 아닐 때 매치
문자열이 계속해서 반복되는지 조사하는 정규식을 작성하고 싶을 때 사용한다.
그루핑 정규식은 (ABC)+
와 같은 형태로 ()
로 조사하고 싶은 문자열을 묶어준다.
다음 예시를 살펴보자.
>>> p = re.compile('(ABC)+')
>>> m = p.search('ABCABCABC OK?')
>>> print(m)
<re.Match object; span=(0, 9), match='ABCABCABC'>
>>> print(m.group(0))
ABCABCABC
그루핑에 대한 예제를 한번 살펴보자.
>>> p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")
>>> m = p.search("HAN 010-1234-1234")
\w+\s+\d+[-]\d+[-]\d+
는 '이름 + "" + 전화번호'를 찾는 정규식이다. 다음 정규식에서 이름만 추출하고 싶다면 어떻게 해야할까?
>>> p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
>>> m = p.search("HAN 010-1234-1234")
>>> print(m.group(1))
HAN
이름에 해당하는 \w+
를 (\w+)
으로 그룹핑하면 된다. match 객체의 group(인덱스) 메서드를 사용하여 그루핑된 부분의 문자열만 뽑아낼 수 있다.
group(인덱스) | 설명 |
---|---|
group(0) | 매치된 전체 문자열 |
group(1) | 첫 번째 그룹에 해당하는 문자열 |
group(2) | 두 번째 그룹에 해당하는 문자열 |
group(n) | n 번째 그룹에 해당하는 문자열 |
만약 전화번호 부분을 추출하고 싶다면 다음과 같이 그루핑하면 된다.
>>> p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
>>> m = p.search("HAN 010-1234-1234")
>>> print(m.group(2))
010-1234-1234
전화번호에서 국번만 따로 추출하고 싶다면 다음과 같이 그루핑하면 된다.
>>> p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("HAN 010-1234-1234")
>>> print(m.group(3))
010
>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('Paris in the the spring').group()
'the the'
'(\b\w+)\s+\1'
은 (그룹) + "" + 그룹과 동일한 단어
와 매치된다. 이렇게 정규식을 만들면 2개의 동일한 단어를 연속적으로 사용해야 매치된다.
이것을 가능하게 하는 것은 바로 재참조 메타 문자 \1
이다. 1
은 첫 번째 그룹을 가리킨다. 두 번째 그룹을 가리킬려면 \2
를 사용하면 된다.
정규식 안에 그룹이 많아진다면 혼란스러울 것이다. 그리고 정규식이 수정,삭제를 통해 그룹을 변경해야하는 경우라면 위험할 것이다.
이럴 때는 이름으로 참조하는 방법이 있다.
다음 예시를 살펴보자.
(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)
앞의 이름 + 전화번호를 추출하는 예제의 정규식이다.
이름을 추출하여 보지.
>>> p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("HAN 010-1234-1234")
>>> print(m.group("name"))
HAN
전방 탐색에는 긍정형과 부정형 전방 탐색이 있다.
정규식 | 종류 | 설명 |
---|---|---|
(?=...) | 긍정형 전방 탐색 | ...에 해당하는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다. |
(?!...) | 부정형 전방 탐색 | ...에 해당하는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다. |
sub메서드
를 사용하면 정규식과 매치되는 부분을 다른 문자로 쉽게 바꿀 수 있다.예시를 살펴보자.
>>> p = re.compile('(blue|white|red)')
>>> p.sub('color', blue socks and red shoes')
'color socks and color shoes'
딱 한번만 바꾸고 싶은 경우가 있을 수 있다. 이렇게 바꾸기 횟수를 제어하려면 3번째 매개변수로 count
값을 입력하면 된다.
>>> p.sub('color', blue socks and red shoes', count=1)
'color socks and red shoes'
subn메서드
를 사용하면 정규식과 매치되는 부분을 다른 문자로 쉽게 대체하지만 결과값을 튜플로 돌려주고 대체된 횟수를 반환해준다.예시를 살펴보자.
>>> p = re.compile('(blue|white|red)')
>>> p.subn('color', blue socks and red shoes')
('color socks and color shoes', 2)
>>> p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<phone>\g<name>", "Han 010-1234-1234"))
010-1234-1234 Han
'이름 + 전화번호'의 문자열을 '전화번호 + 이름' 문자열로 대체했다.
sub의 바꿀 문자열 부분에 '\g<그룹 이름>'을 사용하면 정규식의 그룹 이름을 참조할 수 있게 된다.
"\g<phone>\g<name>"
대신에 "\g<2>\g<1>"
참조 번호를 사용하여도 똑같은 결과값을 나타낸다.
def hexrepl(match):
value = int(match.group())
return hex(value)
p = re.compile(r'\d+')
p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'
hexrepl
는 16진수로 변환하여 주는 함수이다. 이처럼 sub
메서드의 매개변수로 함수값을 사용해서 활용할 수 있다.
예제를 통해 살펴보자.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>
메타문자 *
는 매우 Greedy(탐욕스러운)해서 매치할 수 있는 최대한의 문자열인 <html><head><title>Title</title>
문자열을 모두 소비해 버린다.
Non-greedy
한 방법을 살펴보자.
Non-greedy
문자인 ?
을 사용하면 *
의 탐욕을 제한할 수 있다.
>>> print(re.match('<.*?>, s).group())
<html>
Non-greedy
문자인 ?
는 *?, +?, ??, {m, n}?
와 같이 사용할 수 있다. 가능한 한 최소한의 반복을 수행하도록 도와준다.