토큰화는 텍스트를 의미 있는 부분으로 나누는 NLP의 기본적인 개념 중 하나이다.
# 예제
doc = nlp('Brexit is the impending withdrawl of the U.K. from the European Union')
for token in doc:
print(token.text)
**수행결과**
Brexit
is
the
impending
withdrawl
of
the
U.K.
from
the
European
Union
spaCy의 토큰화 결과가 만족스럽지 않다면, add_special_case 메소드를 이용해서 나만의 규칙을 추가할 수 있다.
정규 표현식은 패턴 매칭이 가능하므로 처리하고 있는 데이터가 정확하거나 부정확하다는 것을 확인할 수 있다.
# 예제
import re
sentence1 = 'Book me a metro from Airport Station to Hong Kong Station.'
sentence2 = 'Book me a cab from Hong Kong Airport to AsiaWorld-Expo.'
from_to = re.compile('.* from (.*) to (.*)')
to_from = re.compile('.* to (.*) from (.*)')
from_to_match = from_to.match(sentence2)
to_from_match = to_from.match(sentence2)
if from_to_match and from_to_match.groups():
_from = from_to_match.groups()[0]
_to = from_to_match.groups()[1]
print('from_to pattern matched correctly. Printing values')
print('From : {}, To : {}'.format(_from, _to))
elif to_from_match and to_from_match.groups():
_to = to_from_match.groups()[0]
_from = to_from_match.groups()[1]
print('to_from pattern matched correctly. Printing values')
print('From : {}, To : {}'.format(_from, _to))
**수행결과**
from_to pattern matched correctly. Printing values
From : Hong Kong Airport, To : AsiaWorld-Expo.
정규 표현식에서 사용하는 메타 문자
. ^ $ * + ? { } [ ] \ | ( )
문자 클래스 [ ]
[abc]
문자 클래스로 만들어진 정규식은 "[ ] 사이의 문자들과 매치"라는 의미를 갖는다.
예를 들어 [abc]라면 a, b, c 중에 한 개의 문자와 매치를 의미한다. a, before, delete와 매치하면 a는 문자 a가 있으므로 매치, before은 문자 b가 있으므로 매치, delete는 a, b, c 중 어느 하나도 포함하고 있지 않으므로 매치되지 않는다.
[ ] 안의 두 문자 사이에 "-"를 사용하면 두 문자 사이의 범위를 의미한다. [a-c]는 [abc]와 같은 의미를 갖는다.
문자 클래스 안에 ^ 메타 문자를 사용할 경우 not의 의미를 가져 [^0-9]의 경우 숫자가 아닌 문자만 매치되게된다.
별도의 표기법이 몇 가지 존재한다.
- \d : 숫자와 매치, [0-9]와 동일
- \D : 숫자가 아닌 것과 매치 [^0-9]와 동일
- \s : whitespace 문자와 매치, [ \t\n\r\f\v]와 동일
- \S : whitespace 문자가 아닌 것과 매치
- \w : 문자 + 숫자와 매치, [a-zA-Z0-9_]와 동일
- \W : 문자 + 숫자가 아닌 문자와 매치
Dot (.)
a.b
줄바꿈 문자인 \n을 제외한 모든 문자와 매치됨을 의미한다. a.b는 a와 b 사이에 어떤 문자가 들어가도 모두 매치된다는 의미로, aab, a0b는 매치되고, abc는 a와 b 사이에 문자가 없으므로 매치되지 않는다.
a[.]b
[.]와 같이 문자 클래스 내에 Dot 메타 문자가 사용된다면 이것은 문자 .을 의미한다.
반복 (*)
ca*t
*바로 앞에 있는 문자 a가 0부터 무한대로 반복될 수 있다는 의미이다.
ct, cat, caaat 모두 a가 0번 이상 반복되어 매치된다.
반복 (+)
ca+t
+앞의 문자가 최소 1번 이상 반복될 때 사용한다.
ct는 a가 0번 반복되어 매치되지 않지만, cat, caat는 매치된다.
반복 ({m,n}, ?)
반복 횟수가 m부터 n까지 매치된다. 생략된 m은 0과 동일하며, 생략된 n은 무한대의 의미를 갖는다.
ca{2}t
위의 정규식은 "c + a(반드시 2번 반복) + t"의 의미를 갖는다.
cat는 a가 1번만 반복되어 매치되지 않고, caat는 a가 2번 반복되어 매치된다.
ca{2, 5}t
"c + a(2~5회 반복) + t"의 의미를 갖는다.
cat과 caaaaaat는 범위를 벗어나 매치되지 않고, caat와 caaaaat는 매치된다.
ab?c
? 메타 문자는 {0, 1}을 의미한다. 즉, "a + b(있어도 되고 없어도 된다) + c"의 의미를 갖는다.
abc와 ac 둘 다 매치가 된다.
re.compile()
import re
p = re.compile('ab*')
re.compile을 사용하여 정규 표현식을 컴파일한다.
문자열 검색
컴파일된 패턴 객체는 4가지 메소드를 제공한다.
Method | 목적 |
---|---|
match() | 문자열의 처음부터 정규식과 매치되는지 조사한다. |
search() | 문자열 전체를 검색하여 정규식과 매치되는지 조사한다. |
findall() | 정규식과 매치되는 모든 문자열(substring)을 리스트로 돌려준다. |
finditer() | 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 돌려준다. |
match
import re
p = re.compile('[a-z]+')
m = p.match('python') # match 객체 반환
n = p.match('3 python') # None 반환
"python"은 [a-z]+에 부합되므로 match 객체를 반환하고 "3 python"은 부합되지 않아 None을 반환한다.
# 기본 형태
p = re.compile('[a-z]+')
m = p.mathc("python")
# 축약 형태
m = re.match('[a-z]+', "python")
search
m = p.search("python") # <re.Match object; span=(0, 6), match='python'>
n = p.search("3 python") # <re.Match object; span=(2, 8), match='python'>
"3 python"의 첫 번째 문자는 "3"이지만 search는 문자열의 처음부터 검색하는 것이 아니라 문자열 전체를 검색하기 때문에 "3" 이후의 "python" 문자열과 매치된다.
findall
result = p.findall("life is too short")
print(result) # ['life', 'is', 'too', 'short']
"life is too short" 문자열의 'life', 'is', 'too', 'short' 단어를 각각 [a-z]+ 정규식과 매치해서 리스트로 반환한다.
*finditer
result = p.finditer("life is too short")
print(result) # <callable_iterator object at ~>
for r in result: print(r)
# <re.Match object; span=(0, 4), match='life'>
# <re.Match object; span=(5, 7), match='is'>
# <re.Match object; span=(8, 11), match='too'>
# <re.Match object; span=(12, 17), match='short'>
finditer은 findall과 동일하지만 그 결과로 반복 가능한 객체를 돌려준다. 반복 가능한 객체가 포함하는 각각의 요소는 match 객체이다.
match 객체의 메소드
method | 목적 |
---|---|
group() | 매치된 문자열을 돌려준다. |
start() | 매치된 문자열의 시작 위치를 돌려준다. |
end() | 매치된 문자열의 끝 위치를 돌려준다. |
span() | 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다. |
m = p.match("python")
m.group() # 'python'
m.start() # 0
m.end() # 6
m.span() # (0, 6)
컴파일 옵션
- DOTALL(S) : "."이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다.
- IGNORECASE(I) : 대소문자에 관계없이 매치할 수 있도록 한다.
- MULTILINE(M) : 여러줄과 매치할 수 있도록 한다. (^, $ 메타문자 사용과 관계가 있는 옵션이다.)
- VERBOSE(X) : verbose 모드를 사용할 수 있도록 한다. (정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게 된다.)
옵션을 사용할 때 re.DOTALL처럼 전체 옵션 이름을 써도 되고, re.S처럼 약어를 써도 된다.
DOTALL, S
p = re.compile('a.b')
m = p.match('a\nb') # None
p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb') # match 객체 반환
보통 여러 줄로 이루어진 문자열에서 \n에 상관없이 검색할 때 많이 사용한다.
IGNORECASE, I
p = re.compile('[a-z]+', re.I)
p.match('python') # 매치
p.match('Python') # 매치
p.match('PYTHON') # 매치
[a-z]+ 정규식은 소문자만을 의미하지만 대소문자 구별 없이 매치되는 것을 확인할 수 있다.
MULTILINE, M
메타 문자 ^,
p = re.compile("^python\s\w+")
data = """python one
life is too short
python two
you need python
python three"""
print(p.findall(data))
여기서 사용한 정규식은 python이라는 문자열로 시작하고 그 뒤에 whitespace, 그 뒤에 단어가 와야한다는 의미이다.
실행하면 ['python one']이 출력된다.
^ 메타 문자에 의해 python이라는 문자열을 사용한 첫 번째 줄만 매치된 것이다.
^ 메타문자를 문자열 전체의 처음이 아니라 각 라인의 처음으로 인식시키고 싶을 때 사용하는 옵션이 re.MULTILINE, re.M이다.
p = re.compile("^python\s\w+", re.MULTILINE)
data = """python one
life is too short
python two
you need python
python three"""
print(p.findall(data))
이렇게 수정하면 ['python one', 'python two', 'python three']가 출력된다. 즉, re.MULTILINE은 ^, $ 메타 문자를 문자열의 각 줄마다 적용해 주는 것이다.
VERBOSE, X
charref = re.compile(r"""
&[#] # Start of a numeric entity reference
(
0[0-7]+ # Octal form
| [0-9]+ # Decimal form
| x[0-9a-fA-F]+ # Hexadecimal form
)
; # Trailing semicolon
""", re.VERBOSE)
re.VERBOSE 옵션을 사용하면 문자열에 사용된 whitespace는 컴파일할 때 제거된다(단 []안에 사용한 whitespace는 제외).
백슬래시
\section이라는 문자열을 찾고 싶은데 그냥 넣으면 [ \t\n\r\f\v]ection으로 인식해버린다. 그래서 \\section으로 사용해야 하는데 컴파일할 때 \section이 전달되어 버리는데 이를 막기위해서는 문자열 앞에 r을 붙여주면 된다. 그러면 정규식을 Raw String임을 알려주는 것이 된다.
p = re.compile(r'\\section')