드디어 6개월 장정의 네이버 커넥트재단에서 주관하는 부스트캠프 AI-Tech를 마무리했습니다. 마무리 프로젝트를 하면서 다양한 고민과 공부를 했지만 블로그로 따로 정리는 하지 못해 몰아서 정리를 해보려 합니다.
첫번째는 텍스트 전처리 과정에서 많이 활용했던 re 모듈입니다.
re 모듈은 파이썬에서 정규표현식을 다룰 수 있는 대표적인 모듈로 텍스트에서 어떤 규칙을 기반으로 문자열의 위치, 교체 등을 할 수 있어 유용하게 활용했습니다.
우선 정규표현식이란 무엇인지 먼저 알아보겠습니다.
정규표현식이란 복잡한 문자열을 처리할 때 사용하는 기법입니다. 정규표현식은 어떤 정해진 규칙을 기반으로 문자열을 정의하고 이를 기반으로 문자열을 처리하기 때문에 효과적입니다.
간단한 예시로 "a,b,c 중 한개의 문자와 매치" 되는 문자열을 찾아보겠습니다.
text = "dajhukw"
if a in text or b in text or c in text:
return True
p = re.compile([abc])
m = p.search(text)
print(m)
a,b,c 중 한개의 문자와 매치 라는 간단한 조건 하에 정규표현식은 간단히 [abc] 만으로 표현하였습니다.
이렇듯 복잡한 문자열 속에서 반복문과 조건문을 과도하게 활용할 필요 없이 문자열 처리가 가능하다는 점이 정규표현식의 가장 큰 장점 입니다.
정규표현식의 다양한 패턴 표현에 대해서는 아래를 참고하시면 됩니다.
https://wikidocs.net/4308
re 모듈은 정규식을 컴파일해 패턴의 형태로 활용할 수도 있고, 다양한 메서드를 활용해 문자열을 검색하고 교체할 수 있습니다.
compile 메서드는 정규표현식을 컴파일해 패턴 객체를 생성합니다. 이 패턴객체는 다양한 메서드에 적용되어 문자열을 처리함에 있어 조건문 처럼 작동합니다.
import re
p = re.compile('ab*')
위의 정규식('ab*')은 반복을 의미하는 * 메타 문자를 사용했습니다. 정규식의 정의에 따라 b가 0번 이상 반복되는 패턴을 p 변수에 저장했습니다. p변수는 다른 문자열에 조건문으로 활용되어 메서드에 따라 원하는 값을 반환할 수 있습니다.
다음은 문자열 검색에 활용되는 메서드들 입니다.
| Method | 목적 |
|---|---|
| match() | 문자열의 처음부터 정규식과 매치되는지 조사한다. |
| search() | 문자열 전체를 검색하여 정규식과 매치되는지 조사한다. |
| findall() | 정규식과 매치되는 모든 문자열(substring)을 리스트로 리턴한다. |
| finditer() | 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 리턴한다. |
아래의 정규식을 활용해 문자열 검색에 활용할 패턴을 만들어 보겠습니다. 아래의 정규식은 a-z까지의 언어로 이루어져 있는지를 확인합니다.
import re
p = re.compile('[a-z]+')
match 메서드는 문자열의 처음부터 끝까지 정규식과 매치되는지 확인합니다.
m = p.match("python")
print(m) #<re.Match object; span=(0, 6), match='python'>
"python" 문자열은 정규식에 부합하기 때문에 0부터 6자리 까지 매칭된다는 match 객체가 리턴되었습니다.
search 메서드는 match와는 다르게 문자열 속에서 패턴에 부합하는 문자열이 있는지 찾아주는 메서드입니다.
m = p.search("python")
print(m) #<re.Match object; span=(0, 6), match='python'>
탐색 문자열이 "python"인 경우에는 모든 문자열이 패턴에 일치함으로 match와 동일한 결과를 반환합니다. 그러나 아래의 경우는 다릅니다.
m = p.search("3 python")
print(m) #<re.Match object; span=(2, 8), match='python'>
"3"은 패턴에 일치하지 않기 때문에 반환된 값에는 포함되지 않고 문자열의 2에서 8번째 값인 python만 포함되어 매치됩니다.
이 때 span 값을 통해 문자열에서 해당 패턴의 첫번째 매치되는 문자열의 인덱스를 확인할 수 있기 때문에 이 역시 유용하게 활용됩니다.
findall 메서드는 문자열에서 패턴에 일치하는 모든 값을 찾아 리스트로 반환합니다.
result = p.findall("life is too short")
print(result) #['life', 'is', 'too', 'short']
주어진 문자열에서 공백(" ")은 패턴에 포함되지 않으므로 공백을 기준으로 문자열이 구분되어 리스트 형태로 저장된 것을 볼 수 있습니다.
finditer의 동작 방식은 findall과 동일하지만 리스트 형태로 문자열을 반환하는 것이 아닌 반복가능한 match 객체를 반환한다는 점이 다릅니다.
result = p.finditer("life is too short")
print(result) #<callable_iterator object at 0x01F5E390>
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'>
search는 위의 반복문을 통해 생성된 match 객체 중 첫번째만 반환하지만 finditer는 패턴과 일치하는 모든 문자열을 구분해 반환하기 때문에 전처리에 필요한 인덱싱에 더 유용한 면이 있습니다.
위의 메소드들을 활용해서 생성된 match 객체에는 이 match 객체들만의 메소드가 있습니다. 인덱싱에 특히 유용하니 기억하기 바랍니다!
| method | 목적 |
|---|---|
| group | 매치된 문자열을 리턴한다. |
| start | 매치된 문자열의 시작 위치를 리턴한다. |
| end | 매치된 문자열의 끝 위치를 리턴한다. |
| span | 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 리턴한다. |
m = p.search("3 python")
m.group() # 'python'
m.start() # 2
m.end() # 8
m.span() #(2, 8)
제가 무엇보다 유용하게 정규표현식을 활용한 메서드는 sub 메서드 입니다. 특정 패턴의 문자열을 제거하거나 문자열을 치환할 때 많이 활용하였습니다. 사용법은 간단합니다.
import re
text = "Life is too short"
print(re.sub('too ','',text)) # Life is short
print(re.sub('too','not',text)) # Life is not short
re.sub 모듈은 기본적으로 치환에 목표를 두고 있습니다. 그러나 치환할 문자를 공백('')으로 지정하면 해당 패턴의 문자열을 제거할 수 있습니다.
프로젝트 중에 실제로 활용했던 전처리 코드입니다.
파이썬 문자열의 기본 메서드인 replace를 활용할수도 있지만, re.sub 메소드를 활용해 패턴을 활용해 불필요한 반복을 줄이고 한 번에 전처리가 가능했습니다.
def clean_text(text):
# \\n -> \n 으로 변경
text = text.replace('\\n', '\n')
# 단독 \ 제거
text = re.sub(r'(?<!\n)\\(?!n)', '', text)
return text
오늘은 문자열 전처리에 필수로 활용되는 re 모듈을 공부해봤습니다. 이 외에도 다양한 기능도 있기 때문에 궁금하시다면 공식문서 를 참고하시면 됩니다. 또한 여기서 소개해드리지 못한 다양한 정규표현식은 추후에 포스팅 하도록 하겠습니다. 좋은 하루 되세요:)