[Python] 파이썬 정규표현식 - re 모듈

8 minute read

이 포스팅은 다음 내용들에 대해 다룹니다.

  • 정규 표현식
  • 파이썬의 re 모듈
  • 정규식을 이용한 문자열 검색
  • 정규식을 이용한 문자열 치환과 분리
  • match 객체의 메서드
  • 그루핑
  • 컴파일 옵션
  • 백슬래시 문제


정규 표현식


정규 표현식의 기초, 메타 문자

정규 표현식에서 사용하는 메타 문자(meta characters)에는 다음과 같은 것들이 있다.

. ^ $ * + ? { } [ ] \ | ( )

메타 문자란 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자를 말한다.


문자 클래스 [ ]

[abc] 는 단어에 a,b,c 중 하나라도 있으면 매치된다.

[abc]는 [a-c]와 동일한 표현이며, 마찬가지로 [012345]는 [0-5]와 동일하다. 즉 하이픈(-)은 ‘~’의 의미를 가진다.

  • [a-zA-Z] : 알파벳 모두
  • [0-9] : 숫자


문자 클래스 [ ] 안에는 아무 문자나 올 수 있지만, ^가 올 경우 주의해야 한다. 문자 클래스 안에 오는 ‘ ^ ‘는 반대(not)의 의미를 갖게 된다.

즉, [^0-9]라는 정규 표현식은 숫자가 아닌 문자만 매치된다.


[자주 사용하는 문자 클래스]

[0-9] 또는 [a-zA-Z] 등은 무척 자주 사용하는 정규 표현식이다. 이렇게 자주 사용하는 정규식은 별도의 표기법으로 표현할 수 있다. 다음을 기억해 두자.

  • \d - 숫자와 매치, [0-9]와 동일한 표현식이다.
  • \D - 숫자가 아닌 것과 매치, [^0-9]와 동일한 표현식이다.
  • \s - whitespace 문자와 매치, [ \t\n\r\f\v]와 동일한 표현식이다. 맨 앞의 빈 칸은 공백문자(space)를 의미한다.
  • \S - whitespace 문자가 아닌 것과 매치, [^ \t\n\r\f\v]와 동일한 표현식이다.
  • \w - 문자+숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일한 표현식이다.
  • \W - 문자+숫자(alphanumeric)가 아닌 문자와 매치, [^a-zA-Z0-9_]와 동일한 표현식이다.

대문자로 사용된 것은 소문자의 반대임을 추측할 수 있다.



Dot(.)

정규 표현식의 Dot(.) 메타 문자는 줄바꿈 문자인 \n을 제외한 모든 문자와 매치된다.

a.b라는 정규 표현식은 a + 모든문자 + b와 매치된다.

예를 들면 다음과 같다.

  • “aab”는 가운데 문자 “a”가 모든 문자를 의미하는 .과 일치하므로 정규식과 매치된다.
  • “a0b”는 가운데 문자 “0”가 모든 문자를 의미하는 .과 일치하므로 정규식과 매치된다.
  • “abc”는 “a”문자와 “b”문자 사이에 어떤 문자라도 하나는있어야 하는 이 정규식과 일치하지 않으므로 매치되지 않는다.


문자 ‘ . ‘ 그대로의 의미를 사용하고 싶다면, a[.]b처럼 사용하면 된다. 이 정규표현식은 a.b와는 매치되고 acb와는 매치되지 않는다.

즉, 문자 클래스([ ]) 내에 Dot( . ) 메타 문자가 사용되면 문자 ‘ . ‘ 그대로를 의미한다.



반복(*)

* 메타 문자는 바로 앞에 있는 문자가 무한대로 반복될 수 있다는 의미이다.

예를 들어 ca*t는 다음과 같은 문자열들과 모두 매치된다.

정규식 문자열 Match 여부 설명
ca*t ct Yes “a”가 0번 반복되어 매치
ca*t cat Yes “a”가 0번 이상 반복되어 매치 (1번 반복)
ca*t caaat Yes “a”가 0번 이상 반복되어 매치 (3번 반복)



반복 (+)

+ 메타 문자도 마찬가지로 반복을 나타내는데, *와 다르게 1번 이상 반복되어야 한다.

정규식 문자열 Match 여부 설명
ca+t ct No “a”가 0번 반복되어 매치되지 않음
ca+t cat Yes “a”가 1번 이상 반복되어 매치 (1번 반복)
ca+t caaat Yes “a”가 1번 이상 반복되어 매치 (3번 반복)



반복 ({m,n}, ?)

{ } 메타 문자를 사용하면 반복 횟수를 고정할 수 있다. {m, n} 정규식을 사용하면 반복 횟수가 m부터 n까지 매치할 수 있다.

또한 m 또는 n을 생략할 수도 있다.

만약 {3,}처럼 사용하면 반복 횟수가 3 이상인 경우이고 {,3}처럼 사용하면 반복 횟수가 3 이하를 의미한다. 생략된 m은 0과 동일하며, 생략된 n은 무한대(2억 개 미만)의 의미를 갖는다.


반복은 아니지만 비슷한 개념으로 ? 메타 문자가 있다. 이 메타 문자는 {0,1}과 동일한 의미를 갖는 것으로, 앞의 문자가 1번만 있거나 없거나라는 뜻이다.

예를 들면 다음과 같다.

정규식 문자열 Match 여부 설명
ab?c abc Yes “b”가 1번 사용되어 매치
ab?c ac Yes “b”가 0번 사용되어 매치


*, +, ? 메타 문자는 모두 {m, n} 형태로 고쳐 쓰는 것이 가능하지만 가급적 이해하기 쉽고 표현도 간결한 *, +, ? 메타 문자를 사용하는 것이 좋다.



|

| 메타 문자는 or과 동일한 의미로 사용된다. A|B라는 정규식이 있다면 A 또는 B라는 의미가 된다.

p = re.compile('Crow|Servo')
m = p.match('CrowHello')
print(m)
<re.Match object; span=(0, 4), match='Crow'>



^

^ 메타 문자는 문자열의 맨 처음과 일치함을 의미한다. 앞에서 살펴본 컴파일 옵션 re.MULTILINE을 사용할 경우에는 여러 줄의 문자열일 때 각 줄의 처음과 일치하게 된다.

print(re.search('^Life', 'Life is too short'))
<re.Match object; span=(0, 4), match='Life'>

print(re.search('^Life', 'My Life'))
None

^Life 정규식은 Life 문자열이 처음에 온 경우에는 매치하지만 처음 위치가 아닌 경우에는 매치되지 않음을 알 수 있다.



$

$ 메타 문자는 ^ 메타 문자와 반대의 경우이다. 즉 $는 문자열의 끝과 매치함을 의미한다.

print(re.search('short$', 'Life is too short'))
<re.Match object; span=(12, 17), match='short'>

print(re.search('short$', 'Life is too short, you need python'))
None

short$ 정규식은 검색할 문자열이 short로 끝난 경우에는 매치되지만 그 이외의 경우에는 매치되지 않음을 알 수 있다.

^ 또는 $ 문자를 메타 문자가 아닌 문자 그 자체로 매치하고 싶은 경우에는 \^, \$ 로 사용하면 된다.



파이썬의 re 모듈


파이썬은 정규 표현식을 지원하기 위해 re ^regular\ expression^ 모듈을 제공한다.

import re
p = re.compile('ab*')

re.compile을 사용하여 정규 표현식(위 예에서는 ab*)을 컴파일한다. re.compile의 결과로 돌려주는 객체 p(컴파일된 패턴 객체)를 사용하여 그 이후의 작업을 수행할 수 있다.



정규식을 이용한 문자열 검색


컴파일된 패턴 객체의 메서드를 사용하여 문자열 검색을 수행할 수 있다.

Method 목적
match() 문자열의 처음부터 정규식과 매치되는지 조사한다.
search() 문자열 전체를 검색하여 정규식과 매치되는지 조사한다.
findall() 정규식과 매치되는 모든 문자열(substring)을 리스트로 돌려준다.
finditer() 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 돌려준다.


match, search는 정규식과 매치될 때는 match 객체를 돌려주고, 매치되지 않을 때는 None을 돌려준다. 이들 메서드에 대한 간단한 예를 살펴보자.


예시에서는 다음과 같은 패턴을 사용합니다.

import re
p = re.compile('[a-z]+')


match

match 메서드는 문자열의 처음부터 정규식과 매치되는 지 검사합니다.

m = p.match("python")
print(m)
<_sre.SRE_Match object at 0x01F3F9F8>

m = p.match("3 python")
print(m)
None

일반적으로 다음과 같이 사용됩니다.

p = re.compile('[a-z]+')
m = p.match( 'string goes here' )
if m:
    print('Match found: ', m.group())
else:
    print('No match')
    
out:
  Match found: string



search 메서드는 문자열 전체를 검사하기 때문에 다음과 같은 결과가 나타납니다.

m = p.search("python")
print(m)
<_sre.SRE_Match object at 0x01F3FA68>

m = p.search("3 python")
print(m)
<_sre.SRE_Match object at 0x01F3FA30>



findall

findall 메서드는 문자열 전체를 검사하여 정규식과 매치해서 리스트로 반환합니다.

result = p.findall("life is too short")
print(result)
['life', 'is', 'too', 'short']



finditer

finditer 메서드는 findall과 동일하지만 정규식과 매치되는 각각의 요소를 match 객체로 반환합니다.

result = p.finditer("life is too short")
print(result)
<callable_iterator object at 0x01F5E390>

for r in result: print(r)
<_sre.SRE_Match object at 0x01F3F9F8>
<_sre.SRE_Match object at 0x01F3FAD8>
<_sre.SRE_Match object at 0x01F3FAA0>
<_sre.SRE_Match object at 0x01F3F9F8>



정규식을 이용한 문자열 치환과 분리


sub, subn

sub와 subn은 문자열에서 정규식과 매치되는 요소들을 원하는 요소로 치환해주며, subn은 반환하는 값이 치환된 문자열과 더불어 치환된 개수의 튜플이라는 점 이외에는 sub와 동일합니다.

sub 메서드는 다음과 같은 파라미터들을 갖습니다.

  • repl: 대신할 문자열
  • string: 대상 문자열
  • count: 치환 횟수

사용법은 다음과 같습니다.

s = 'Gorio, Gorio, Gorio keep a straight face.'
p = re.compile(r'Gorio')
replaced_s = p.sub( repl='Ryan', count=2, string=s)
print(replaced_s)

out:
	Ryan, Ryan, Gorio keep a straight face.



split

split 메서드는 파이썬 문자열의 기본 메서드인 split과 유사하지만 정규식을 처리할 수 있습니다.

s = "100-200*300-500+20"
p = re.compile(r'\D')
p.split(s)

out:
  ['100', '200', '300', '500', '20']


만약 구분자까지 포함하는 리스트를 얻고 싶다면, ( )메타 문자로 그루핑을 사용할 수 있습니다(그루핑은 뒤에서 설명합니다).

s = "100-200*300-500+20"
p = re.compile(r'(\D)')
p.split(s)

out:
  ['100', '-', '200', '*', '300', '-', '500', '+', '20']



match 객체의 메서드


match 객체는 다음곽 같은 메서드들을 가집니다.

method 목적
group() 매치된 문자열을 돌려준다.
start() 매치된 문자열의 시작 위치를 돌려준다.
end() 매치된 문자열의 끝 위치를 돌려준다.
span() 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다.


예를 들면 다음과 같습니다.

m = p.match("python")
m.group()
'python'
m.start()
0
m.end()
6
m.span()
(0, 6)

match 메서드는 문자열의 처음부터 검색하기 때문에 start( ) 메서드의 결과는 항상 0일 수 밖에 없습니다.


search 메서드의 경우 다음과 같은 결과가 나올 수 있습니다.

m = p.search("3 python")
m.group()
'python'
m.start()
2
m.end()
8
m.span()
(2, 8)


모듈 단위로 수행하기

지금까지 re.compile 로 생성한 패턴 객체를 이용해 match, search 등의 메서드를 사용했지만 이를 다음과 같이 축약된 형태로 사용할 수 있습니다.

p = re.compile('[a-z]+')
m = p.match("python")
# 같은 표현
m = re.match('[a-z]+', "python")

특정 패턴을 한 번만 사용할 경우에 편리합니다.



그루핑


ABC 문자열이 계속해서 반복되는지 조사하는 정규식을 작성하고 싶다고 하자. 어떻게 해야할까? 지금까지 공부한 내용으로는 위 정규식을 작성할 수 없다. 이럴 때 필요한 것이 바로 그루핑(Grouping) 이다.

위 경우는 다음처럼 그루핑을 사용하여 작성할 수 있다.

(ABC)+

그룹을 만들어 주는 메타 문자는 바로 ( )이다.

p = re.compile('(ABC)+')
m = p.search('ABCABCABC OK?')
print(m)
<re.Match object; span=(0, 9), match='ABCABCABC'>
print(m.group())
ABCABCABC



컴파일 옵션


정규식을 컴파일할 때 다음 옵션을 사용할 수 있다.

  • DOTALL(S) - . 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다.
  • IGNORECASE(I) - 대소문자에 관계없이 매치할 수 있도록 한다.
  • MULTILINE(M) - 여러줄과 매치할 수 있도록 한다. (^, $ 메타문자의 사용과 관계가 있는 옵션이다)
  • VERBOSE(X) - verbose 모드를 사용할 수 있도록 한다. (정규식을 보기 편하게 만들수 있고 주석등을 사용할 수 있게된다.)

옵션을 사용할 때는 re.DOTALL처럼 전체 옵션 이름을 써도 되고 re.S처럼 약어를 써도 된다.



백슬래시 문제


정규 표현식을 파이썬에서 사용할 때 혼란을 주는 요소가 한 가지 있는데, 바로 백슬래시(\)이다.

예를 들어 어떤 파일 안에 있는 "\section" 문자열을 찾기 위한 정규식을 만든다고 가정해 보자.

\section

이 정규식은 \s 문자가 whitespace로 해석되어 의도한 대로 매치가 이루어지지 않는다.

위 표현은 다음과 동일한 의미이다.

[ \t\n\r\f\v]ection

의도한 대로 매치하고 싶다면 다음과 같이 변경해야 한다.

\\section

즉 위 정규식에서 사용한 \ 문자가 문자열 자체임을 알려 주기 위해 백슬래시 2개를 사용하여 이스케이프 처리를 해야 한다.

따라서 위 정규식을 컴파일하려면 다음과 같이 작성해야 한다.

p = re.compile('\\section')


그런데 여기에서 또 하나의 문제가 발견된다. 위처럼 정규식을 만들어서 컴파일하면 실제 파이썬 정규식 엔진에는 파이썬 문자열 리터럴 규칙에 따라 \\\로 변경되어 \section이 전달된다.

✋ 이 문제는 위와 같은 정규식을 파이썬에서 사용할 때만 발생한다(파이썬의 리터럴 규칙). 유닉스의 grep, vi 등에서는 이러한 문제가 없다.

결국 정규식 엔진에 \\ 문자를 전달하려면 파이썬은 \\\\처럼 백슬래시를 4개나 사용해야 한다.

✋ 정규식 엔진은 정규식을 해석하고 수행하는 모듈이다.

p = re.compile('\\\\section')


만약 위와 같이 \를 사용한 표현이 계속 반복되는 정규식이라면 너무 복잡해서 이해하기 쉽지않을 것이다. 이러한 문제로 인해 파이썬 정규식에는 Raw String 규칙이 생겨나게 되었다. 즉 컴파일해야 하는 정규식이 Raw String임을 알려 줄 수 있도록 파이썬 문법을 만든 것이다. 그 방법은 다음과 같다.

p = re.compile(r'\\section')

위와 같이 정규식 문자열 앞에 r 문자를 삽입하면 이 정규식은 Raw String 규칙에 의하여 백슬래시 2개 대신 1개만 써도 2개를 쓴 것과 동일한 의미를 갖게 된다.

✋ 만약 백슬래시를 사용하지 않는 정규식이라면 r의 유무에 상관없이 동일한 정규식이 될 것이다.



위 내용은 여기를 참조하였으며, 더 많은 내용을 확인하실 수 있습니다.



Leave a comment