본문 바로가기
Computer/Python

정규 표현식 (Regular expression) 1

by 방구석 과학자 2023. 12. 3.

 

 

정규 표현식 (Regular expression, Regex) 이란?

정규 표현식은 문자의 패턴을 나타내는 '메타 문자'입니다. '메타(Meta)'라는 단어에서 느낄 수 있듯, 문자를 나타내는 문자라는 뜻입니다. 어떤 특정 텍스트의 특정 패턴을 파악하고자 등장한 문자들로, 1951년 미국의 수학자 Stephen Cole Kleene에 의해 처음 고안되었으며, 1968년에 1. 텍스트 에디터의 특정 패턴을 찾기 위해, 2. 컴파일러의 어휘적 분석을 위해 사용되면서, 크게 유행하기 시작했습니다. 앞으로 파이썬을 이용한 정규 표현식을 다뤄보도록 하겠습니다.

 

Python을 이용한 정규 표현식의 간단한 예제

예를 들어 'Hello, world!'라는 문자열에서 문자가 아닌 부분을 찾고 싶다고 해봅시다. Python 은 're'라는 정규 표현식을 위한 라이브러리를 제공합니다. 이를 사용해 아래와 같이 코드를 작성할 수 있습니다.

import re

s = "Hello, world!"
p = re.compile(r"\W")
result = p.search(s)
print(result)

# 결과 : <re.Match object; span=(5, 6), match=','>

아래에서 추가로 설명하겠지만, \W 는 단어가 아닌 문자를 의미합니다. (알파벳 대소문자, 숫자, _ 를 제외한 다른 모든 문자) 위 코드의 결과로 \W 가 s [5:6]에 위치하며, 그 단어는 쉼표 ',' 임을 알 수 있습니다. 

이처럼, compile 안에 메타 문자 패턴을 이용해 문자열의 실제 패턴을 파악할 수 있습니다.

 

한 가지 주의해야 할 점! Python 은 raw string 을 사용하지 않으면 리터럴 규칙에 따라 '\' 를 전달하기 위해 '\\' 로 두 번 써야 하기 때문에, 이용자의 편의와 가독성을 위해 compile 안의 문자는 무조건 raw string 사용을 추천합니다.

 

정규 표현식 Table

Python 을 이용해 정규표현식을 맛보기 전에 메타 문자인 정규 표현식의 단어가 무엇을 의미하는지 아래 표를 통해 확인해 봅시다. 매칭되는 문자열은 파란색, 매칭이 안되는 문자열은 빨간색으로 예시에 표시하겠습니다. 

 

메타 문자 설명 예시
[ ] $\cdot$  대괄호 안에 있는 문자들 중 하나와 매칭
$\cdot$  '-' 를 통해 범위 설정 가능
   (ex. [a-zA-Z] : 알파벳 대소문자들 중 하나를 의미)
$\cdot$  c[abc]t : cat
$\cdot$  c[def]t : cat
$\cdot$  s[ui]n : sun / sin / san
[^ ] $\cdot$  대괄호 안에 있는 문자들을 제외한 문자들 중 하나와 매칭
   (ex. [^a-zA-Z] : 알파벳 대소문자들을 제외한
   나머지 문자들 중 하나를 의미)
$\cdot$  s[^ui]n : sun / sin / san
\d $\cdot$  모든 숫자 중 하나와 매칭 
$\cdot$  [0-9] 와 동일
$\cdot$  hi\d : hi5 / hi?
\D $\cdot$  숫자를 제외한 모든 문자들 중 하나와 매칭
$\cdot$  [^0-9] 와 동일
$\cdot$  hi\D : hi5 / hi?
\w $\cdot$  모든 문자 + 숫자 중 하나와 매칭
$\cdot$  [a-zA-Z0-9_] 와 동일
$\cdot$  co\wch : coach / co@ch
\W $\cdot$  모든 문자 + 숫자를 제외한 문자들 중 하나와 매칭
$\cdot$  [^a-zA-Z0-9_] 와 동일
$\cdot$  co\Wch : coach / co@ch
\s $\cdot$  화이트스페이스(whitespace) 문자 하나와 매칭
$\cdot$  [ \t\n\r\f\v] 와 동일
' ' : 스페이스
\t : 탭
\n : 개행 (newline)
\r : 
$\cdot$  file\sname : file name / file_name
\S $\cdot$  화이트스페이스(whitespace) 가 아닌 문자 하나와 매칭
$\cdot$  [^ \t\n\r\f\v] 와 동일
$\cdot$  file\Sname : file name / file_name
. $\cdot$  \n 을 제외한 모든 문자 하나와 매칭
$\cdot$ [.] 는 메타 문자가 아닌 '.' 을 의미함에 주의
$\cdot$  c.t : cat cut / c@t
{n,m} $\cdot$ 앞 문자의 반복 횟수가 n번 이상, m번 이하이면 매칭
$\cdot$ {n} : 앞 문자가 n번 반복일 때만 매칭
$\cdot$ {n,} :앞 문자가 n번 이상 / {,m} : m번 이하일 때만 매칭
$\cdot$ ca{2,3}t : cat / caat / caaat / caaaat
$\cdot$ ca{2}t :cat / caat / caaat
* $\cdot$ 앞 문자의 반복 횟수가 0 번 이상이면 매칭
$\cdot$ {0,} 와 동일
$\cdot$ c[au]*t : ct / cat / cauauut
+ $\cdot$ 앞 문자의 반복 횟수가 1 번 이상이면 매칭
$\cdot$ {1,} 와 동일
$\cdot$ c[au]+t : ct / cut / cauauut
? $\cdot$ 앞 문자가 1번 있거나, 없으면 매칭
$\cdot$ {0,1} 와 동일
$\cdot$ c[au]?t : ct / cut / cauauut
| $\cdot$ A|B : A 혹은 B 를 의미 $\cdot$ cat|dog : cat / dog / catdog
^ $\cdot$ 한 줄의 맨 앞을 의미
$\cdot$ Python 에서는 re.MULTILINE 을 사용해야 줄 마다 매칭 가능
$\cdot$ ^this : this\ndog
$\cdot$ ^dog : this\ndog
\A $\cdot$ 문자열의 맨 앞을 의미 $\cdot$ \Athis : this\ndog
$\cdot$ \Adog : this\ndog
$ $\cdot$ 한 줄의 맨 끝을 의미
$\cdot$ Python 에서는 re.MULTILINE 을 사용해야 줄 마다 매칭 가능
$\cdot$ this$ : this\ndog
$\cdot$ dog$ : this\ndog
\Z $\cdot$ 문자열의 맨 끝을 의미 $\cdot$ this\Z : this\ndog
$\cdot$ dog\Z : this\ndog
\b $\cdot$ 단어 구분자(word boundary)로 보통 화이트스페이스
$\cdot$ (^\w|\w$|\W\w|\w\W) 와 같으나, 문자를 소비하지 않음
$\cdot$ \bmy\b : oh my god / mystery
\B $\cdot$ 단어 구분자가 아닌 모든 문자를 의미
$\cdot$ \b 와 마찬가지로 문자를 소비하지 않음
$\cdot$ my\B : oh my god / mystery
( ) $\cdot$ 중괄호 안의 패턴을 그룹핑(grouping) $\cdot$ c([au]t)+ : catatat / catutat /caatt

 

Greedy Quantifier (탐욕적인 수량자) 

*, +, ? 처럼 횟수를 나타내는 메타 문자 같은 경우, 정규 표현식은 'greedy' matching을 우선적으로 채택한다.

예를 들어 패턴 oh+ 를  이용해 ohhh 문자열에 매칭해보자. oh, ohh, ohhh 모두 패턴 oh+ 에 해당하나, 정규 표현식은 탐욕적(greedy)이기 때문에, h+ 가 가장 많은 문자열을 먹을 수 있는 ohhh 가 매칭되게 된다.

import re

s = "ohhh"
p = re.compile(r"oh+")
result = p.search(s)
print(result) # 결과 : <re.Match object; span=(0, 4), match='ohhh'>

 

Lazy Quantifier (게으른 수량자)

그렇다면, s = "file_name : [name], user : [user]"에서 file_name : [name]을 찾기 위해 아래와 같이 코드를 작성했다고 가정해 보자.

import re

s = "file_name : [name], user : [user]"
p = re.compile(r"file_name : \[.*\]")
result = p.search(s)
print(result) # 결과 : <re.Match object; span=(0, 33), match='file_name : [name], user : [user]'>

 

패턴 p를 이용해 name 만 .* 에 매칭시키려 의도했으나, 정규 표현식의 greedy 성질 때문에 .* 가 'name], user : [user'에 매칭된 것을 알 수 있다. 이를 방지하기 위해 아래와 같이 'lazy' 매칭을 이용할 수 있다. 

import re

s = "file_name : [name], user : [user]"
p = re.compile(r"file_name : \[.*?\]") # Lazy : ? 
result = p.search(s)
print(result) # 결과 : <re.Match object; span=(0, 18), match='file_name : [name]'>

 

.* 대신 .*? 로 lazy 매칭을 진행하면, 우리가 의도했던 대로 가장 짧게 매칭되는 문자열 'file_name : [name]'을 찾을 수 있다.

 

소비(consumption)의 개념

위의 정규 표현식 표에서 |, ^, \A, $, \Z, \b, \B는 문자열을 소비하지 않습니다. 문자열을 소비하지 않는다는 의미는 예를 들어 'c\wt' 패턴을 이용해 cat 매칭시킬 때, \w는 문자 a를 반환하게 되는데, 이를 '소비한다' 라고 표현합니다. 문자열을 소비하지 않는 메타 문자들은 모두 문자열 내에서 어떤 특정 패턴과 매칭되지만, 실제로는 어떤 단어를 실제로 반환하지 않고 그냥 그 패턴이 있다! 만 확인합니다. (A|B 같은 경우는 A 혹은 B 가 매칭되면, 매칭된 문자열을 반환하고, 나머지 B 혹은 A는 반환하지 않고 넘어갑니다. 두 패턴 중 하나를 소비하지 않는다는 의미에서 | 는 문자열을 소비하지 않는다라고 말합니다.)

 

다음 포스팅에서는 부정형/긍정형 전방탐색/후방탐색에 대해서 알아보고, 실제 파이썬 코드를 사용하는 방법을 공부해 보겠습니다.

 

반응형

'Computer > Python' 카테고리의 다른 글

파이썬으로 Hotstring 기능 구현하기 2  (0) 2022.09.03
파이썬으로 Hotstring 기능 구현하기 1  (0) 2022.08.15
왜 파이썬인가?  (0) 2022.07.10

댓글