본문 바로가기
개인공부/Python

[Python] 정규표현식 완전정복!

by BuyAndPray 2020. 12. 7.
반응형

 

* 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

 

반응형

댓글