* Regular Expression HOWTO를 참고하여 작성되었습니다.
정규표현식이란?
정규표현식(정규식)은 복잡한 문자열 처리를 위해 사용되는 기법으로 특정 문자열 내에서 내가 찾고자 하는 패턴을 쉽게 찾을 수 있도록 도와준다. 정규표현식은 파이썬만을 위해서 개발된 것은 아니고, 문자열 처리를 하는 모든 곳에서 사용될 수 있다.
파이썬에서는 re 모듈을 이용해서 정규표현식을 활용할 수 있다.
import re
정규표현식 사용하기
1. 정규표현식 컴파일
정규표현식을 사용하기 위해서는 제일 먼저 내가 찾고자 하는 정규식 패턴을 정규식 패턴 객체(<re.Pattern> 클래스 객체)로 변환하는 과정이 필요하고 이것이 정규표현식 컴파일이다.
예를 들어 "한국 주식에 투자하는 게 미국 주식에 투자하는 것보다 낫다."라는 문장에서 "주식"이라는 단어를 찾고자 할 때 내가 "주식"이라는 패턴을 찾으려고 한다는 것을 알려줘야 한다.
import re
sentence = "한국 주식에 투자하는 게 미국 주식에 투자하는 것보다 낫다."
pattern = re.compile("주식")
pattern # re.compile('주식')
type(pattern) # <class 're.Pattern'>
2. 정규표현식 매칭
정규표현식을 컴파일하고 나면 다음과 같이 <re.Pattern> 클래스의 객체가 리턴이 된다. 이 객체에는 여러 가지 메서드와 속성들이 있는데 자주 사용하는 메서드는 아래와 같다.
메서드 | 역할 |
match() | 문자열의 시작부터 내가 찾고자 하는 패턴이 있는지 확인한다. 있는경우) Match object 반환 없는경우) None 반환 |
search() | 문자열 모든위치에서 내가 찾고자 하는 패턴이 있는지 확인한다. (처음 매칭되는 부분 문자열만 리턴) 있는경우) Match object 반환 없는경우) None 반환 |
findall() | 문자열 내에서 찾고자 하는 패턴과 일치하는 모든 부분 문자열을 list로 리턴한다. 있는경우) 부분 문자열들의 리스트 없는경우) 빈 리스트 |
아래와 같이 match()는 문자열의 시작부터 패턴을 탐색하지만, search()는 문자열 모든 위치에 대하여 해당 패턴이 있는지 확인한다.
import re
sentence = "한국 주식에 투자하는 게 미국 주식에 투자하는 것보다 낫다."
pattern = re.compile("주식")
# '한국'으로 시작하기 때문에 None 리턴
pattern.match(sentence) # None
# 문자열 모든 위치에 대해서 검색(처음 일치하는 부분 문자열)
pattern.search(sentence) # <re.Match object; span=(3, 5), match='주식'>
# 패턴에 해당되는 부분 문자열을 list로 리턴
pattern.findall(sentence) # ['주식', '주식']
re.Match Object
<re.Pattern> 클래스 객체의 match()와 search() 메서드를 사용하면 <re.Match> 클래스 객체가 생성된다. 패턴과 일치한 문자열을 출력하기 위해서는 <re.Match> 객체 메서드를 사용해야 한다.
메서드 | 역할 |
group() | 일치하는 문자열을 반환 |
start() | 일치하는 문자열의 시작 위치 반환 |
end() | 일치하는 문자열의 끝 위치 반환 |
span() | 일치하는 문자열의 (시작위치, 끝위치) 튜플 반환 |
import re
sentence = "한국 주식에 투자하는 게 미국 주식에 투자하는 것보다 낫다."
pattern = re.compile("주식")
match = pattern.search(sentence)
# 일치하는 문자열 반환
match.group() # 주식
# 일치하는 문자열의 시작 위치 반환
match.start() # 3
# 일치하는 문자열의 끝 위치 반환
match.end() # 5
# 일치하는 문자열의 (시작 위치, 끝 위치) 튜플 반환
match.span() # (3, 5)
메타 문자 활용하기
사실 지금까지는 굳이 정규표현식을 사용하지 않고도 파이썬의 find() 메서드를 활용해 쉽게 처리할 수 있었다. 하지만 정규표현식을 사용하는 이유는 메타 문자(meta characters)를 포함하고 있기 때문이다.
예를 들어 "저희 집 앞에 있는 중국집 전화번호는 02-123-4567입니다."라는 문장에서 전화번호를 추출하고 싶을 때 문자열 처리 함수로만 코딩한다고 하면 매우 번거로울 것이다. 이 문제를 메타 문자를 활용해 간단히 해결할 수 있다.
정규표현식에서 메타 문자는 일반 문자(a, b, 1, 2 등)와 다르게 자신과 일치하지 않고 특수한 역할을 하는 문자들이다. 메타 문자는 아래와 같이 총 12개만 존재한다.
. ^ $ * + ? { } [ ] \ | ( ) |
import re
sentence = "()[]"
pattern1 = re.compile("()")
pattern2 = re.compile("[]")
# 메타 문자는 자기 자신과 일치하지 않음
# 특별한 역할을 위해 사용되기에 일반 문자와 동일하게 사용할 경우 에러 발생
pattern1.search(sentence) # 에러 발생
pattern2.search(sentence) # 에러 발생
각각의 메타 문자들은 서로 다른 역할을 하는데 역할을 표로 정리하면 아래와 같다.
메타 문자 | 역할 |
[ ] | [ ] 사이의 문자들과 매치. '-'으로 문자의 범위 표현 가능 |
. | 줄바꿈(\n)을 제외한 모든 문자와 매치 |
\ | 이스케이프 처리 또는 숫자, 문자 집합 표현(\d, \s 등) |
* | 바로 앞에 있는 문자가 0부터 무한 번까지 반복 |
+ | 바로 앞에 있는 문자가 1부터 무한 번까지 반복 |
{m, n} | 바로 앞에 있는 문자 m~n번 반복 |
? | {0, 1}과 동일 |
| | or과 동일 (A|B - A 또는 B를 찾아라) |
^ | 문자열의 맨 처음과 일치함 |
$ | 문자열의 맨 끝과 일치함 |
# 메타 문자 활용 예시
import re
sentence = "My phone number is 010-2451-7878 and my age is 31."
# []
# [] 사이의 문자 중 한 개와 매칭
# [abcd] : a, b, c, d 중에 매칭되는 한 개 탐색, [a-d]와 동일
re.compile("[abcd]").search(sentence) # <re.Match object; span=(12, 13), match='b'>
# [a-z] : 소문자, [A-Z] : 대문자, [a-zA-Z] : 알파벳 전체, [0-9] : 숫자
re.compile("[0-9]").search(sentence) # <re.Match object; span=(19, 20), match='0'>
# .
# 줄바꿈(\n)을 제외한 모든 문자(한 개)와 매칭
# a.e : a + 모든 문자 + e (예, age, ace, a2e a1e 등)
re.compile("a.e").search(sentence) # <re.Match object; span=(40, 43), match='age'>
# \
# 이스케이프 처리, 숫자/문자 집합 표현
# 메타 문자를 이스케이프 시켜서 탐색 가능하게 한다.
re.compile("\(").search("[]()") # <re.Match object; span=(2, 3), match='('>
# \d : 숫자와 매치, [0-9]와 동일
# \D : 숫자가 아닌 것과 매치
# \s : 공백과 매치
# \S : 공백이 아닌 것과 매치
# \w : 문자 + 숫자와 매치, [a-zA-Z0-9_]와 동일
# \w : 문자 + 숫자가 아닌 것과 매치
re.compile("\d").search(sentence) # <re.Match object; span=(19, 20), match='0'>
re.compile("\D").search(sentence) # <re.Match object; span=(0, 1), match='M'>
re.compile("\s").search(sentence) # <re.Match object; span=(2, 3), match=' '>
# *
# 바로 앞에 있는 문자가 0부터 무한 번 반복
# a*b : a가 0부터 무한 번 반복 (예, b, ab, aab, aaab, aaaaaaab 등)
re.compile("a*b").search("aaaaaaaaaab") # <re.Match object; span=(0, 11), match='aaaaaaaaaab'>
# +
# 바로 앞에 있는 문자가 1부터 무한 번 반복
# a+b : a가 1부터 무한 번 반복 (예, ab, aab, aaaab 등) - b는 안됨
re.compile("a+b").search("ab") # <re.Match object; span=(0, 2), match='ab'>
re.compile("a+b").search("b") # None
# {}
# {m,n} : 앞에 문자가 m ~ n 번 반복
# {1,} : +와 동일, {0,} : *와 동일, {m,} : 반복 횟수 m ~ 무한대, {,n} : 반복 횟수 0~n
# a{2}b : a를 무조건 2번 반복
re.compile("a{2}b").search("ab") # None
re.compile("a{2}b").search("aab") # <re.Match object; span=(0, 3), match='aab'>
# a{3,}b : a를 3번 이상 반복
re.compile("a{3,}b").search("aab") # None
re.compile("a{3,}b").search("aaaaaaaab") # <re.Match object; span=(0, 9), match='aaaaaaaab'>
# ?
# {0,1}과 동일 : 앞에 문자가 0 ~ 1 번 반복
# ab?e : b가 0번 또는 한 번 반복
re.compile("ab?c").search("abc") # <re.Match object; span=(0, 3), match='abc'>
re.compile("ab?c").search("abbc") # None
# |
# or과 동일 A|B : A 또는 B 와 매치
# dog|cat : 개 또는 고양이와 매치
re.compile("dog|cat").search("that dog is so fast") # <re.Match object; span=(5, 8), match='dog'>
re.compile("dog|cat").search("that cat is so fast") # <re.Match object; span=(5, 8), match='cat'>
# ^
# 문자열의 맨 처음과 일치
re.compile("^dog").search("dog is not a cat") # <re.Match object; span=(0, 3), match='dog'>
re.compile("^dog").search("that dog is so fast") # None
# $
# 문자열의 맨 끝과 일치
re.compile("dog$").search("I like that dog") # <re.Match object; span=(12, 15), match='dog'>
re.compile("dog$").search("I like that dog, but i dont like that") # None
'개인공부 > Python' 카테고리의 다른 글
[Python] heapq 모듈 사용법 (0) | 2020.10.30 |
---|---|
[Python] Input vs. sys.stdin.readline 차이점? (2) | 2020.10.02 |
[Python] Mutable vs. Immutable 차이점? (0) | 2020.09.29 |
댓글