본문 바로가기
Python/정규 표현식

정규 표현식 1 [Python / 파이썬]

by Deeppago 2022. 1. 4.

파이썬 문법 중 정규 표현식이라는 것이 있는데, 자주 사용하지 않다 보니 자꾸 까먹는다. 그래서 정규 표현식을 사용할 때마다 구글링 해서 사용하곤 했다. 구글링 없이 정규식을 사용하기 위해 자주 사용되는 정규 표현식을 내 나름대로 정리해보려 한다.

참고 자료 : https://wikidocs.net/4308

 

1. 메타 문자

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

 

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

 

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

 

정규 표현식에 위 메타 문자를 사용하면 특별한 의미를 갖게 된다.

자, 그러면 가장 간단한 정규 표현식부터 시작해 각 메타 문자의 의미와 사용법을 알아보자.

 

1.1 문자 클래스 [ ]

문자 클래스로 만들어진 정규식은 "[ ] 사이의 문자들과 매치"라는 의미를 갖는다.

정규 표현식이 [abc]라면 이 표현식의 의미는 "a, b, c 중 한 개의 문자와 매치"를 뜻한다. 이해를 돕기 위해 문자열 "before"가 정규식 [abc]와 어떻게 매치되는지 살펴보자. match 메서드는 아래에서 자세히 설명하도록 하겠다.

 

s = 'before'
result = re.match('[abc]', s)
print(result[0])
>>> 'b'

 

"before"는 정규식과 일치하는 문자인 "b"가 있으므로 "b"와 매치되는 것을 확인할 수 있다.

 

[ ] 안의 두 문자 사이에 하이픈(-)을 사용하면 두 문자 사이의 범위(From - To)를 의미한다. 예를 들어 [a-c]라는 정규 표현식은 [abc]와 동일하고 [0-5]는 [012345]와 동일하다.

다음은 하이픈(-)을 사용한 문자 클래스의 사용 예이다.

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

문자 클래스([ ]) 안에는 어떤 문자나 메타 문자도 사용할 수 있지만 주의해야 할 메타 문자가 1가지 있다. 그것은 바로 ^인데, 문자 클래스 안에 ^ 메타 문자를 사용할 경우에는 반대(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_]와 동일한 표현식이다.

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

 

1.2 Dot(.)

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

※ 아래에서 설명하겠지만 정규식을 작성할 때 re.DOTALL 옵션을 주면 \n 문자와도 매치된다.

 

다음 정규식을 보자.

a.b

 

위 정규식의 의미는 다음과 같다.

"a + 모든 문자 + b"

 

즉 a와 b라는 문자 사이에 어떤 문자가 들어가도 모두 매치된다는 의미이다.

이해를 돕기 위해 문자열 "aab"가 정규식 a.b와 어떻게 매치되는지 살펴보자.

 

s = 'aab'
result = re.match('a.b', s)
print(result)
print(result[0])
>>> <re.Match object; span=(0, 3), match='aab'>
>>> aab

 

"aab"는 가운데 문자 "a"가 모든 문자를 의미하는. 과 일치하므로 정규식과 매치된다.

 

1.3 반복(*)

다음 정규식을 보자.

 

ca*t

 

이 정규식에는 반복을 의미하는 * 메타 문자가 사용되었다. 여기에서 사용한 *은 * 바로 앞에 있는 문자 a가 0부터 무한대로 반복될 수 있다는 의미이다.

 

※ 여기에서 * 메타 문자의 반복 개수가 무한대라고 표현했는데 사실 메모리 제한으로 2억 개 정도만 가능하다고 한다.

 

즉 다음과 같은 문자열이 모두 매치된다.

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

 

1.4 반복(+)

반복을 나타내는 또 다른 메타 문자로 +가 있다. +는 최소 1번 이상 반복될 때 사용한다. 즉 *가 반복 횟수 0부터라면 +는 반복 횟수 1부터인 것이다.

다음 정규식을 보자.

 

ca+t

 

위 정규식의 의미는 다음과 같다.

 

"c + a(1번 이상 반복) + t"

 

위 정규식에 대한 매치 여부는 다음 표와 같다.

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

 

*와 + 반복 메타 문자의 차이점은 최소 0번 반복이냐 1번 반복이냐이다!

 

1.5 ?

반복은 아니지만 이와 비슷한 개념으로 ? 이 있다. ? 메타문자가 의미하는 것은 {0, 1} 이다.

다음 정규식을 보자.

 

ab?c

 

위 정규식의 의미는 다음과 같다:

"a + b(있어도 되고 없어도 된다) + c"

 

위 정규식에 대한 매치여부는 다음 표와 같다.

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

2. 파이썬에서 정규 표현식을 지원하는 re 모듈

파이썬은 정규 표현식을 지원하기 위해 re(regular expression의 약어) 모듈을 제공한다. re 모듈은 파이썬을 설치할 때 자동으로 설치되는 기본 라이브러리로 사용 방법은 다음과 같다.

 

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

 

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

  • ※ 정규식을 컴파일할 때 특정 옵션을 주는 것도 가능한데, 이에 대해서는 뒤에서 자세히 살펴본다.
  • ※ 패턴이란 정규식을 컴파일한 결과이다.

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

이제 컴파일된 패턴 객체를 사용하여 문자열 검색을 수행해 보자. 컴파일된 패턴 객체는 다음과 같은 4가지 메서드를 제공한다.

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

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

 

※ match 객체란 정규식의 검색 결과로 돌려주는 객체이다.

 

3.1 match

match 메서드는 문자열의 처음부터 정규식과 매치되는지 조사한다. 아래 예시를 보자.

 

s = "python"
p = re.compile('[a-z]+')
m = p.match(s)
print(m)
>>> <re.Match object; span=(0, 6), match='python'>

 

"python" 문자열은 [a-z]+ 정규식에 부합되므로 match 객체를 돌려준다.

 

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

 

"3 python" 문자열은 처음에 나오는 문자 3이 정규식 [a-z]+에 부합되지 않으므로 None을 돌려준다.

 

3.2 search

이번에는 search 메서드를 수행해 보자.

 

"""문자열 전체를 검색하여 정규식과 매치되는지 조사"""
s = "3 python"
p = re.compile('[a-z]+')
m = p.search(s)
print(m)
>>> <re.Match object; span=(2, 8), match='python'>

 

"3 python" 문자열의 첫 번째 문자는 "3"이지만 search는 문자열의 처음부터 검색하는 것이 아니라 문자열 전체를 검색하기 때문에 "3 " 이후의 "python" 문자열과 매치된다.

이렇듯 match 메서드와 search 메서드는 문자열의 처음부터 검색할지의 여부에 따라 다르게 사용해야 한다.

 

3.3 findall

이번에는 findall 메서드를 수행해 보자.

 

"""
정규식과 매치되는 모든 문자열(substring)을 리스트로 돌려준다
숫자이거나 문자 모두 찾기
"""
s = "3 33 life is too short"
p = re.compile('[0-9]+|[a-z]+')
m = p.findall(s)
print(m)
>>> ['3', '33', 'life', 'is', 'too', 'short']

4. match 객체의 매서드

자, 이제 match 메서드와 search 메서드를 수행한 결과로 돌려준 match 객체에 대해 알아보자. 앞에서 정규식을 사용한 문자열 검색을 수행하면서 아마도 다음과 같은 궁금증이 생겼을 것이다.

  • 어떤 문자열이 매치되었는가?
  • 매치된 문자열의 인덱스는 어디서부터 어디까지인가?

match 객체의 메서드를 사용하면 이 같은 궁금증을 해결할 수 있다. 다음 표를 보자.

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

다음 예로 확인해 보자.

 

>>> s = "3 python"
>>> p = re.compile('[a-z]+')
>>> m = p.search(s)
>>> print(m)
>>> print(m.group()) #매치된 문자열을 돌려준다.
>>> print(m.start()) #매치된 문자열의 시작 위치를 돌려준다.
>>> print(m.end()) #매치된 문자열의 끝 위치를 돌려준다.
>>> print(m.span()) #매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다.
<re.Match object; span=(2, 8), match='python'>
python
2
8
(2, 8)

 

[모듈 단위로 수행하기]

지금까지 우리는 re.compile을 사용하여 컴파일된 패턴 객체로 그 이후의 작업을 수행했다. re 모듈은 이것을 좀 축약한 형태로 사용할 수 있는 방법을 제공한다. 다음 예를 보자.

 

>>> p = re.compile('[a-z]+')
>>> m = p.match("python")

 

위 코드가 축약된 형태는 다음과 같다.

 

>>> m = re.match('[a-z]+', "python")

 

위 예처럼 사용하면 컴파일과 match 메서드를 한 번에 수행할 수 있다. 보통 한 번 만든 패턴 객체를 여러번 사용해야 할 때는 이 방법보다 re.compile을 사용하는 것이 편하다.


5. 컴파일 옵션

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

  • DOTALL(S) - . 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다.
  • IGNORECASE(I) - 대소문자에 관계없이 매치할 수 있도록 한다.
  • MULTILINE(M) - 여러줄과 매치할 수 있도록 한다. (^, $ 메타문자의 사용과 관계가 있는 옵션이다)

 

5.1 DOTALL, S

. 메타 문자는 줄바꿈 문자(\n)를 제외한 모든 문자와 매치되는 규칙이 있다. 만약 \n 문자도 포함하여 매치하고 싶다면 re.DOTALL 또는 re.S 옵션을 사용해 정규식을 컴파일하면 된다.

다음 예를 보자.

 

"""'.' 메타 문자는 줄바꿈 문자 \n을 제외한 모든 문자와 매치되는 규칙이 있다."""
>>> p = re.compile('a.b')
>>> m = p.match('a\nb')
>>> print(m)
None

 

정규식이 a.b인 경우 문자열 a\nb는 매치되지 않음을 알 수 있다. 왜냐하면 \n은 . 메타 문자와 매치되지 않기 때문이다. \n 문자와도 매치되게 하려면 다음과 같이 re.DOTALL 옵션을 사용해야 한다.

 

"""re.DOTALL 또는 re.S 옵션을 사용하면 \n문자와도 매치가 가능하다."""
>>> p = re.compile('a.b', re.DOTALL)
>>> m = p.match('a\nb')
>>> print(m)
<re.Match object; span=(0, 3), match='a\nb'>

 

5.2 IGNORECASE, I

re.IGNORECASE 또는 re.I 옵션은 대소문자 구별 없이 매치를 수행할 때 사용하는 옵션이다. 다음 예제를 보자.

 

"""대소문자 구별 없이 매치를 수행할때 사용하는 옵션"""
>>> p = re.compile('[a-z]+', re.I)
>>> print(p.match('python'))
>>> print(p.match('Python'))
>>> print(p.match('PYTHON'))
<re.Match object; span=(0, 6), match='python'>
<re.Match object; span=(0, 6), match='Python'>
<re.Match object; span=(0, 6), match='PYTHON'>

 

[a-z] 정규식은 소문자만을 의미하지만 re.I 옵션으로 대소문자 구별 없이 매치된다.

 

5.3 MULTILINE, M

다음 예를 보자.

 

""" 
^ 메타 문자는 문자열의 처음을 의미하고, $는 문자열의 마지막을 의미한다.
예를 들어 ^python은 문자열의 처음이 항상 python으로 시작해야 매치되고,
만약 정규식이 python&이라면 문자열의 마지막은 항상 python으로 끝나야 매치된다.
"""
>>> p = re.compile("^python\s\w+")
>>> data = """python one
 life is too short
 python two
 you need python
 python three"""
>>> print(p.findall(data))
['python one']
"""
re.MULTILINE 또는 re.M 옵션은 
^메타문자를 문자열 전체의 처음이 아니라 각 라인의 처음으로 인식 시켜줄 수 있다.
"""
>>> p = re.compile("^python\s\w+", re.MULTILINE)
>>> data = """python one
life is too short
python two
you need python
python three"""
>>> print(p.findall(data))
['python one', 'python two', 'python three']

6. 백슬래시 문제

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

 

\section

 

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

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

 

\\section

 

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

 

"""정규식 엔진에 \문자를 전달하려면 \를 두번사용하여 이스케이프 처리를 해야한다."""
>>> s = "\\section"
>>> p = re.compile('\\\\section')
>>> m = p.match(s)
<re.Match object; span=(0, 8), match='\\section'>

 

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

 

"""아래와 같이 row string임을 알려주면 \를 그대로 전달할 수 있다."""
>>> s = "\\section"
>>> p = re.compile(r'\\section')
>>> m = p.match(s)
<re.Match object; span=(0, 8), match='\\section'>

'Python > 정규 표현식' 카테고리의 다른 글

정규 표현식 2 [Python / 파이썬]  (0) 2022.01.04

댓글