데이터 분석

파이썬 - 정규표현식 알아보기 (기초)

Jerry Jun 2020. 12. 2. 19:42
728x90

데이터 중에 문자를 분석하는 경우가 있을 것이다.

복잡한 문자열을 처리할 때 빠질 수 없는 것이 "정규 표현식" 이다.

정규 표현식에 대해 알아보자.

 

정규표현식을 제대로 들어가기 전에 어떤 기능을 사용하는지 예시를 살펴보자.

# 주민등록번호 뒷자리를 * 로 변경하기
minsu = '950213-1390192'

한 명의 주민등록번호가 있다고 가정할 때 뒷자리(7개)를 '*' 로 변경하고 싶다고 하자.

정규표현식을 사용하지 않는다면 꽤 긴 코드가 나올 것이다.

하지만 정규표현식을 사용한다면?

 

import re

setting = re.compile("(\d{6})[-]\d{7}")
print(setting.sub("\g<1>-*******", minsu))
------------------------------------------
950213-*******

다음과 같이 잘 나오는 것으로 알고 있다. 정규표현식은 어떠한 언어든 동일한 형식을 가지고 있기 때문에 파이썬이 아니더라도 유용하게 사용할 수 있다.

 

잠깐 보아도 지금 알아야 할 것은 "import re" 와 "compile" 그리고 "sub" 가 보인다. 형식에 대해서도 많이 배워야 한다. 

 

 

첫 번째 : 문자 다루기

#1 문자는 대괄호 !

문자를 파싱할 때는 대괄호[ ] 를 사용합니다. 만약 [abcde] 가 있다고 가정했을 때

  • "a" 가 있다면 매치 성공
  • "enjoy" 가 있다면 'e' 가 존재하므로 매치 성공
  • "xyz" 가 있다면 어느하나 매치되지 않으므로 패스가 됩니다.

#2 연속되는 문자는 '-' !

그렇다면 소문자 모두를 매치하고 싶을 때 [abcdefghijklmnopqrstuvwxyz] 로 한다면 너무 길어지기 때문에 이 때 사용하는 것이 "-" 입니다. 소문자 전체를 매치하고 싶다면 [a-z] 로 하면 됩니다. 대문자를 하고 싶다면 [A-Z] 가 될 것입니다. 소문자와 대문자 전체를 하고 싶다면? [a-zA-Z]가 됩니다.

 

  • [a-z] : 영어 소문자 전체
  • [A-Z] : 영어 대문자 전체
  • [0-9] : 숫자 전체

#3 모든 문자와 매치할 때는 Dot(.) !

만약 어느 문자나 숫자 등이 올 지 모를 때 사용하는 것이 바로 dot(.) 이다. dot 은 줄바꿈(\n)을 제외한 모든 문자와 매치가 가능하다. 예를 들어 'a.c' 라고 정규표현식을 썼을 때 "abc" 도 가능하고 "adc", "a2c" 또한 매치에 성공한다.

 

 

#4 반복 문자 매치할 때는 별표(*) !

몇 번이 연속되는 문자도 매치하고 싶을 떄 사용하는 것이 별표(*) 이다. 별표 이전에 있는 문자를 판단한다. 예를 들어 [ab*c] 라는 정규표현식을 썼을 때 대상은 b 가 될 것이다. b가 0번 이상 반복된다면 매치에 성공한다. 특이한 점이 바로 0번도 포함된다는 것이다. 0번이 포함되지 않는 반복 매치는 플러스(+) 를 사용하면 된다.

  • "ac" : b가 0번 반복 되었다. 매치 성공
  • "abc" : b가 1번 반복 되었다. 매치 성공
  • "abbbbbbc" : b가 6번 반복 되었다. 매치 성공
  • "abbbb" : c 가 없으므로 매치 실패.

 

#5 반복되는 문자수를 지정하고 싶을 때는 중괄호 사용하기!

만약 내가 'abbbc' 라고 b 가 3번만 반복되는 문자를 찾고 싶을 때 사용하는 것은 중괄호{ } 이다.

표현식은 [ab{3}c] 이다. 이렇게 한다면 b가 3번 반복 이외에는 매치되지 않는다.

  • 표현식 'ab{3}c' --> "abbbc" 매치 성공
  • 표현식 'ab{3}c' --> "abc" 매치 실패
  • 표현식 'ab{1,3}c' --> "abc" 매치 성공 (1,3 이므로 1이상 3이하이다.)
  • 표현식 'ab?c' --> "ac" 매치 성공 (물음표는 0개 또는 1개 있을 때를 말한다. {0,1} 과 같음)

 

# 기타 문자 클래스

정규 표현식 설명
\d 숫자를 매치한다. [0-9] 와 같음
\D 숫자가 아닌 것과 매치한다. [^0-9] 와 같음
\s 공백문자. space 나 tab 같은 공백과 매치한다.
\t, \n, \r, \f, \v 와 같음.
\S \s에 해당 안되는 것과 매치된다.
\w 문자 + 숫자와 매치된다. [a-zA-Z0-9_]와 같다.
\W \w 와 반대된다.

이제 파이썬을 통해 실제 정규표현식을 사용하는 시간을 가져보자.

import re

setting = re.compile('ab?c')

기본적으로 re 를 import 하고 compile 안에 지금까지 배운 표현식을 넣습니다.

그리고 표현식을 통해 매치하는 4가지 방법을 실행하겠습니다.

 

 

(1) match

p = re.compile('ab?c')
m = p.match('1 abbbbc')
print(m)
------------------------
None

import re

p = re.compile('ab?c')
m = p.match('1 abc')
print(m)
------------------------
None

match는 들어오는 문자열 전체가 표현식에 해당하지 않는다면 None 값을 반환합니다.

match 객체의 주요 메소드는 다음과 같습니다.

  • start( ) : 매치된 문자열의 시작 위치 리턴
  • end( ) : 매치된 문자열의 마지막 위치 리턴
  • group( ) : 매치된 문자열 리턴
  • span( ) : 매치된 문자열의 (시작 위치, 마지막 위치) 값을 튜플형태로 리턴
p = re.compile('[a-z]+')
m = p.match('minsu')
print(m.start())
print(m.end())
print(m.group())
print(m.span())
-------------------------
0
5
minsu
(0, 5)

 


 

(2) search

p = re.compile('ab?c')
m = p.search('1 abbbbc')
print(m)
--------------------------
None

p = re.compile('ab?c')
m = p.search('1 abc')
print(m)
--------------------------
<_sre.SRE_Match object; span=(2, 5), match='abc'>

match 와는 다르게 1 이 있지만 abc는 표현식에 해당되므로 'abc'가 반환되는 모습을 볼 수 있습니다.

 

 

(3) findall (return list)

p = re.compile('[a-z]+')
m = p.findall('Hi my name is minsu')
print(m)
------------------------------------
['i', 'my', 'name', 'is', 'minsu']

findall 은 표현식에 매치되는 문자를 모두 가져오는 방식이다.

위의 표현식은 반복이 가능한 영어 소문자를 표현했고, 예시문 "Hi my name is minsu" 에서 H 만 대문자 이므로 반환되지 않았다.

 

 

(4) finditer

p = re.compile('[a-z]+')
m = p.finditer('Hi my name is minsu')
print(m)
------------------------------------------------
<callable_iterator object at 0x00000235E76E7208>


p = re.compile('[a-z]+')
m = p.finditer('Hi my name is minsu')
for i in m:
	print(i)
--------------------------------------
<_sre.SRE_Match object; span=(1, 2), match='i'>
<_sre.SRE_Match object; span=(3, 5), match='my'>
<_sre.SRE_Match object; span=(6, 10), match='name'>
<_sre.SRE_Match object; span=(11, 13), match='is'>
<_sre.SRE_Match object; span=(14, 19), match='minsu'>

finditer 는 반환값으로 iterator object 가 되기 때문에 출력값을 보고 싶을 때는 하나하나 출력해야 한다.

 

 


[ compile 옵션 알아보기 ]

 

re.compile( ) 을 사용하면서 표현식을 넣는 것 이외에 다른 기능도 있다.

 

옵션 설명 예시
DOTALL 또는 S dot(.) 은 줄바꿈은 제외하지만
DOTALL 을 사용하면 줄바꿈을 포함한다.
p = re.compile('a.c', re.DOTALL)
m = p.match('a\nb')
IGNORECASE 또는 I 대소문자 구별을 무시하고 매치시켜준다. p = re.compile('[a-z]', re.I)
print(p.match('minsu'))  # 매치 성공
print(p.match('Minsu'))  # 매치 성공
print(p.match('MINSU')) # 매치 성공

MULTILINE 또는 M 새로운 줄마다 새로운 시작으로 생각함. p = re.compile('^name', re.M)
words = '''name hello # 매치
hungry man
name what # 매치
'''
VERBOSE 또는 X 정규표현식이 길어질 때 나누어 쓰는 기능 p = re.compile('''정규표현식
....
...
...''', re.VERBOSE)

 


 

[ 메타 문자 알아보기 ]

 

1) ' | ' (shift + \)

p = re.compile('name|minho')
m = p.match('nameminsu')
print(m)
----------------------------
<_sre.SRE_Match object; span=(0, 4), match='name'>

OR 의 뜻으로 자주 쓰이는 ' | ' 이다. compile 구문에서 'name' 또는 'minho' 가 있다면 매치에 성공한다.

 

 

2) ^

p = re.compile('^name')
m = p.search('name minsu')
n = p.search('minsu name')
print(m)
print(n)
--------------------------
<_sre.SRE_Match object; span=(0, 4), match='name'>
None

'^' 문자는 처음 시작되는 문자인지 알아보는 것으로 예시 m 에서는 name 이 첫 단어로 나왔으므로 정상적으로 반환되었고 두 번째 예시문 n 에서는 name 이 첫 단어가 아니기 때문에 None 값을 반환하였습니다.

 

 

3) $

p = re.compile('name$')
m = p.search('name minsu')
n = p.search('minsu name')
print(m)
print(n)
----------------------------
None
<_sre.SRE_Match object; span=(6, 10), match='name'>

'^' 문자와는 반대로 마지막 단어가 무엇인지 판단하는 문자입니다.

 

 

4) \b

p = re.compile(r'\bname\b')
m = p.search('myname minsu')
n = p.search('minsu name')
o = p.search('name is minsu')
print(m)
print(n)
print(o)
-------------------------------
None
<_sre.SRE_Match object; span=(6, 10), match='name'>
<_sre.SRE_Match object; span=(0, 4), match='name'>

\b는 공백을 의미합니다. 

r 을 통해 raw string 이란 것을 선언해주고 첫 번째 예시 m 은 name앞에 공백이 없으므로 None 값 반환.

예시 n 은 name 앞 뒤로 공백이 있으므로 정상 값 반환. (끝에 아무것도 없어도 공백이라고 판단함을 알 수 있다.)

예시 o 는 name 앞 뒤로 공백이 있으므로 정상 값 반환. (처음에 아무것도 없어도 공백이라고 판단함을 알 수 있다.)

 


# 탐색

p = re.compile(".+@")
m = p.search("minsu@naver.com")
print(m)
--------------------------------
<_sre.SRE_Match object; span=(0, 6), match='minsu@'>

만약 이메일 형식이 있을 때 아이디(minsu) 만을 뽑아내고 싶을 때를 생각해보자.

@ 문자 이전은 아이디에 해당한다. 그래서 정규표현식으로 ".+@" 를 통해 아무문자든지 반복하고 @으로 끝나는 것을 정했지만 @까지 반환되었다. 이럴 때 사용하는 것이 전방 탐색이다.

 

p = re.compile(".+(?=@)")
m = p.search("minsu@naver.com")
print(m)
----------------------------------
<_sre.SRE_Match object; span=(0, 5), match='minsu'>

(?=) 를 사용하게 되면 조건에는 인식하지만 결과에 포함되지 않음을 알 수 있다.

 


p = re.compile(".*[@](?!hanmail).*$")
m = p.search("minsu@naver.com")
n = p.search("minsu@hanmail.com")
o = p.search("minsu@gmail.com")
print(m)
print(n)
print(o)
----------------------------------------
<_sre.SRE_Match object; span=(0, 15), match='minsu@naver.com'>
None
<_sre.SRE_Match object; span=(0, 15), match='minsu@gmail.com'>

이번 예시는 한메일을 제외하는 것이다.

(?!) 를 이용하여 hanmail 만을 걸러내었다. 어떠한 것을 제외하고 싶을 때 쓰인다.

 

 

# 문자열 교체하기

p = re.compile("minsu")
m = p.sub("chulsu", "my name is minsu")
print(m)
---------------------------------------
my name is chulsu

minsu 라는 단어를 chulsu 라는 단어로 교체할 때 sub 를 사용합니다.

 

 

 

300x250