문자열 매칭을 위한 정규식 라이브러리 (re)

7 분 소요

Introduction

크롤링을 하거나, NLP 전처리로 인해 문자열의 특정한 패턴을 찾아야 하는 경우가 있다.
파이썬에서는 find라는 문자열 검색 메소드를 기본으로 지원한다. 문제는 하나의 문자열의 인덱스만을 반환한다.

pattern = "node"
start = -1
text = "This node is root node."
x = text.find(pattern, start + 1) # string.fing(패턴, 시작인덱스) 

>>> 5

여러 개의 문자열 인덱스를 구하려면 반복문을 사용해서 구해야한다.
그러나, 정규 표현식을 사용할 수 있는 re 라이브러리를 사용하면 여러 개의 문자열 인덱스를 한번에 구할 수 있으며, 이외에도 다양한 기능을 제공한다.

모듈 임포트

표준 라이브러리라 import만 하면된다.

import re

사용법

아래 나오는 patten은 정규표현식을 의미하며 뒤에 서술한다.

re.match(pattern, string, flags=0)

string의 시작부터 patten의 매칭여부를 확인하고, 매칭이 되면 True값을 가지는 match 객체를 반환한다.

  • match 객체: 항상 True boolean값을 가지며, 아래 span(시작 인덱스와 끝 인덱스 반환)을 포함해 찾은 패턴의 pos, endpos등 다양한 속성을 가진다.
    이 메소드는 단독으로 거의 안쓰는 것 같고, 시작위치뿐 아니라 모든 인덱스에서 매칭 여부를 찾는 search가 더 잘 쓰인다.
pattern = "node"
text = "This node is root node."
iter = re.finditer(pattern, text)
m = re.match(pattern, text)
print(m)
m = re.search(pattern, text)
print(m.span())

>>> None
>>> (5, 9)

re.finditer(pattern, string, flags=0)

match 객체를 산출하는 이터레이터를 반환한다. match를 이용해 여러 문자열에 대한 시작 인덱스와 끝 인덱스를 구할 수 있다.

pattern = "node"
text = "This node is root node."
iter = re.finditer(pattern, text)
for match in iter:
    print(match.span(), match.start(), match.end(), match.pos, match.endpos)

>>> (5, 9) 5 9 0 23
>>> (18, 22) 18 22 0 23

re.findall(pattern, string, flags=0)

정규표현식에 매칭되는 대상(pattern 매개변수)에 대한 리스트를 반환한다. 정규표현식으로 필터링되는 문자열들을 직접 찾을 때 유용하다.

pattern = r"(*)"
text = "test is done."
all_list = re.findall(pattern, text)
print(all_list)
>>> ['(done)']

re.split(pattern, string, maxsplit=0, flags=0)

정규표현식에 매칭되는 대상(pattern 매개변수)을 기준으로 string을 split한다. 정규표현식에 매칭되는 대상은 없어진다.
maxsplit으로 분할 수를 정할 수 있다.

result = re.split("\s", "I am a boy")
print(result)
result = re.split("[ab]", "I am a boy")
print(result)


>>> ['I', 'am', 'a', 'boy']
>>> ['I ', 'm ', ' ', 'oy']

re.sub(pattern, repl, string, count=0, flags=0)

string에서 pattern에 따라 찾은 문자열들을 모두 repl로 치환한다.

pattern = "node"
text = "This node is root node."
result = re.sub(pattern, "Node", text)
print(result)

>>> This Node is root Node.

re.compile(pattern, flags=0)

re.compile은 먼저 필터링할 표현식을 컴파일한다. 컴파일 이후 결과를 얻은 정규표현식 객체 p를 사용해 실제 필터링 작업을 수행한다.
p는 re 라이브러리에서 지원하는 search, match, split, findall, finditer 등의 메소드를 가진다.
동일 표현식을 반복적으로 사용할 때 유용하다.

p = re.compile("node")
m = p.search("Where is node?")
print(m.span())

>>> (9, 13)

표현식 작성법

정규 표현식을 표현하는 방법들은 여러 종류가 있다.

문자 클래스 ([])

문자 클래스는 대괄호 [] 사이의 문자의 나열로 표현된다.

  • 예를 들면 [ab]라고 하면 a 또는 b가 포함된 문자열만을 매치한다.
  • 하이픈을 써서 범위를 지정할 수 있다. ex) [a-zA-z] [0-9]
  • ^를 사용하면 해당 문자를 포함하지 않는다는 뜻이다. ex) [^0-9]

자주 사용하는 문자 클래스는 이스케이프 문자()를 활용해 다음과 같이 나타낼 수 있다.

  • \d : 숫자와 매칭된다. [0-9]와 동일
  • \D : 숫자가 아닌 것과 매칭.
  • \s : space, tab 등을 포함한 whitespace 문자와 일치.
  • \S : \s가 아닌 문자. 즉 whitespace 문자를 제외한 문자들을 포함한다.
  • \w : 문자와 숫자를 매칭.
  • \W : 문자와 숫자가 아닌 것과 매칭.
text = "He likes chickens."
pattern = r'[^a-di]+s'  # a, b, c, d, i 제외
for match in re.finditer(pattern, text):
    print(match.span(), match.group(0))
    
>>> (5, 8) kes
>>> (13, 17) kens 

메타 문자 ()

아래 문자들은 re에서 특정한 의미를 가지기 때문에, 그냥 사용할 수 없다.

. ^ $ * + ? { } [ ] \ | ( )

이 문자들을 직접 패턴매칭하고 싶은 경우 앞에 이스케이프 문자()를 붙인다.

Dot (.)

.은 일종의 와일드 카드로, \n을 제외한 모든 문자 1개와 매칭된다.

0 개 이상 반복 (*)

는 바로 앞의 문자가 0개 이상일 경우 붙인다.
ex) pattern = r’om
n’로 둘 경우 m이 없거나 1 개 이상 존재

1개 이상 반복 (+)

+는 바로 앞의 문자가 최소 1개 이상 있을 때 붙인다.
ex) pattern = r’om+n’로 둘 경우 m이 1 개 이상 존재

있거나 없거나 (?)

?는 바로 앞의 문자가 있거나 없을 수 있는 모든 경우에 나타낸다.

pattern = r'om?.n'
for match in re.finditer(pattern, text):
    print(match.span(), match.group(0))
  • h = re.sub(‘<.*>’, ‘’, ‘<div>안녕하세요</div>’)
  • h = re.sub(‘<.*?>’, ‘’, ‘<div>안녕하세요</div>’)

반복 ({})

{숫자} 형태로 사용되며, 바로 앞 문제가 숫자만큼 반복될 경우 사용한다.

pattern = r'\w*s{2}'
text = "This post is embarrassing."
for match in re.finditer(pattern, text):
    print(match.span(), match.group(0))

>>> (13, 22) embarrass

or 조건 (|)

여러 개의 정규표현식에 대해 하나랑 매칭한다. 단, 두가지 조건이 만족할 경우 앞의 조건으로만 매칭하고 뒤의 조건은 확인하지 않는다.

그룹 매칭 (())

그룹 매칭은 정규식으로 매칭될 그룹을 ()로 표현한다.

>>> m = re.match('([0-9]+) ([0-9]+)', '8 12')
>>> m.group(1)    # 첫 번째 그룹(그룹 1)에 매칭된 문자열을 반환
'8'
>>> m.group(2)    # 두 번째 그룹(그룹 2)에 매칭된 문자열을 반환
'12'
>>> m.group()     # 매칭된 문자열을 한꺼번에 반환
'8 12'
>>> m.group(0)    # 매칭된 문자열을 한꺼번에 반환
'8 12'

Reference