Python/Python re

Python re : sub

CosmosProject 2021. 3. 10. 21:37
728x90
반응형

 

 

re는 regular expression의 약자로 정규표현식을 의미합니다.

 

Python의 re library에는 sub이라는 method가 있는데 이것은 어떠한 패턴을 내가 원하는 방식으로 대체해줍니다.

 

일반적인 replace 함수는 단지 어떤 문자를 다른 문자로 대체하지만 re의 sub method는 어떤 패턴에 맞는 문자를 대체해주죠.

 

예시를 보기 전에 아래 표는 Python re에서 사용되는 패턴을 나타낼 때 사용되는 기호들입니다.

 

패턴 문자 의미
. 줄바꿈 문자를 제외한 1글자를 의미
^ 문자열의 시작을 의미. 또는 not의 의미.
$ 문자열의 끝을 의미
[] 문자의 집합

e.g.
[xyz] 라고 패턴을 적게되면 x또는 y또는 z라는 문자와 매칭됨.
[x-z] : 이렇게 범위로도 적을 수 있음.
[^a] : a를 제외한 모든 문자를 의미.
| or의 의미

e.g.
a|b : a 또는 b문자를 의미
() 어떤 정규식을 하나의 그룹으로 묶어줌

e.g.
([a-c])([x-z]) : a, b, c와 매칭될 수 있는 그룹 1개와 x, y, z와 매칭될 수 있는 그룹을 의미.

ax, az, by 등은 위 패턴과 매칭됨.

만약 괄호라는 문자 자체를 매칭하고싶으면 \(, \) 등으로 백슬래쉬를 이용한 escape를 해줘야 함.
* 어떤 문자건 0회 이상 반복되는 패턴을 나타냄

e.g.
a* : a가 0번 이상 반복되는 패턴을 나타냄
+ 어떤 문자건 1회 이상 반복되는 패턴을 나타냄

e.g.
a+ : a가 1번 이상 반복되는 패턴을 나타냄
? 어떤 문자가 0회 또는 1회 반복되는 패턴을 나타냄


e.g.
a? : a가 0번 또는 1번 존재하는 패턴을 나타냄
{n} 문자가 n회 반복되는 패턴을 나타냄

e.g.
whi{3}te : whiiite를 의미
{m, n} 문자가 m회 ~ n회 반복되는 패턴을 나타냄

e.g.
wh{2, 4}ite : whhite, whhhite, whhhite를 의미
{m,} 문자가 m회부터 무한대까지 반복되는 패턴을 나타냄

e.g.
wh{2,}ite : whhite, whhhite, whhhhite, whhhhite, .... 등을 나타냄
\w Unicode = 숫자, underscore(_)를 포함하는 모든 언어의 표현가능한 문자.
ASCII = underscore(_)를 포함한 문자 = [a-zA-Z0-9_]와 동일
\W Unicode = 숫자, underscore(_)를 포함하는 모든 언어의 표현가능한 문자를 제외한 나머지 문자
ASCII = underscore(_)를 포함한 문자를 제외한 나머지 문자 = [^a-zA-Z0-9_]와 동일
\d Unicode = 0~9의 숫자를 포함한 모든 숫자
ASCII = [0-9]와 동일
\D Unicode = 0~9의 숫자를 제외한 모든 숫자
ASCII = [^0-9]와 동일
\s Unicode = [\t\n\r\f\v]를 포함하는 공백문자
ASCII = [\t\n\r\f\v]와 동일\t = 탭\n = 줄바꿈\r = 캐리지 리턴\f = 폼피드\v = 수직 탭
\S Unicode = [\t\n\r\f\v]를 제외한 공백문자
ASCII = [^\t\n\r\f\v]와 동일
\b 단어의 시작과 끝에 존재하는 공백
\B 단어의 시작과 끝이 아닌 곳에 존재하는 공백
\[숫자] 표시된 숫자만큼 일치하는 문자열을 의미
\A 문자열의 시작
\Z 문자열의 끝



 

 

 

 

Syntax

re.sub(pattern, new_text, text)

re.sub은 위처럼 사용할 수 있습니다.

text에서

pattern에 맞는 부분을

new_text로 대체하라 라는 의미입니다.

 

 

 

 

 

import re

text = '123abc456'

text_substituted = re.sub(r'[1-3]', '', text)
print(text_substituted)


-- Result
abc456

 

위 예시는

123abc456에서 r'[1-3]'이라는 패턴을 찾아 공백('')으로 바꾼다는 뜻입니다.

[1-3]은 1, 2, 3을 의미하므로 123abc456의 1, 2, 3이 공백으로 바뀌어 abc456만 return 되었습니다.

 

여기서 주의할건 [1-3]이 1, 2, 3을 의미한다고해서 반드시 123 전체를 공백으로 바꾸는 것이 아니라

text에 존재하는 1을 공백으로바꾸고, 2를 공백으로 바꾸고 3도 공백으로 바꾸라는 뜻입니다.

 

r'[1-3]'에서 r은 문자를 있는 그대로 판단하라는 것입니다. 예를들어 백슬래쉬는 escape를 해주는 기능이 있지만 r을 사용하면 백슬래쉬 그 자체로 판단하게 됩니다.

 

 

 

 

 

import re

text = '123abc456'

text_substituted = re.sub(r'[^1-3a-z]', '', text)
print(text_substituted)


-- Result
123abc

위 예시를 봅시다.

^ 기호는 [] 범위 안에서 not을 나타냅니다.

즉, [^1-3a-z]는 1, 2, 3, a ~z의 문자를 제외한 나머지 모든것을 나타냅니다.

이를 토대로 위 코드를 해석해보면 1, 2, 3, a ~ z의 문자를 제외한 나머지를 모두 공백으로 바꾸라는 뜻입니다.

그래서 결과를 보면 123abc만 남고 456은 사라졌습니다.

 

 

 

 

 

import re

text = '123abc456'

text_substituted = re.sub(r'[1-3a-b]', '', text)
print(text_substituted)


-- Result
c456

[1-3a-b]는 1, 2, 3, a, b, c를 의미하므로 text에서 이 문자들이 모두 공백으로 치환되고 남아있는 c456만 return 되었습니다.

 

 

 

 

 

 

 

import re

text = '123abc456'

text_substituted = re.sub(r'(123)(\w*)(45)', '', text)
print(text_substituted)


-- Result
6

(123)(\w*)(45)는 123(0개 이상의 모든문자)45 와 같은 패턴 (12345, 123a45, 123ab45, 123xisjkgkesi45 등)을 의미합니다.

이것을 공백으로 바꾸니까 123abc45가 모두 공백으로 바뀌어 남아있는 6만 리턴되었습니다.

 

 

 

import re

text = '111-1234-5678     000-1234-5678  888-8888-8888'

text_substituted = re.sub(r'(\d{3})-(\d{4})-(\d{4})', r'(\1) \2-\3', text)
print(text_substituted)



-- Result
(111) 1234-5678     (000) 1234-5678  (888) 8888-8888

[위 예시는 전화번호의 형식을 변경하는 것입니다.

 

(\d{3})-(\d{4})-(\d{4}) = \d는 0~9의 모든 숫자를 의미하므로 이 패턴은 숫자3개-숫자4개-숫자4개라는 패턴을 의미합니다.

\1, \2, \3은 패턴에서 괄호로 묶인 group을 의미합니다. 가장 왼쪽부터 첫 번째 그룹(\1)을 의미합니다.

 

'숫자3개-숫자4개-숫자4개'의 패턴을 '(그룹1) 그룹2-그룹3'으로 바꾸라는 뜻입니다.

그리고 순서대로 그룹1 = 숫자3개 / 그룹2 = 숫자4개 / 그룹3 = 숫자4개 이므로 결국

'숫자3개-숫자4개-숫자4개' -> '(숫자3개) 숫자4개-숫자4개'

위처럼 바꾸라는 것입니다.

 

 

 

 

 

 

 

import re


str_test = '[ABCdefGHI]'

sub_test1 = re.sub(r'[a-z]', '', str_test)
print(sub_test1)


sub_test2 = re.sub(r'[a-z]', '', str_test, flags=re.I)
print(sub_test2)



-- Result
[ABCGHI]
[]

re.sub 함수에는 flags라는 argument가 있습니다.

위 예시에는 flags에 re.I라는 값을 주고 있습니다.

re.I는 re.IGNORECASE와 동일한 옵션으로서 이 옵션을 주게 되면 대소문자를 구분하지 않게 됩니다.(Case-insensitive하게 된다는 뜻이죠.)

 

그래서 위 결과를 보면

sub_test1은 a ~ z의 소문자만 사라졌습니다.

하지만 sub_test2에서는 a ~ z인 소문자와 대문자가 모두 사라졌죠. re.I 옵션 때문입니다.

 

 

 

 

 

이번엔 re.sub의 group기능에 대해 알아봅시다.

아래 예시를 보면 이해가 빠를겁니다.

import re


# year = 2020, month = 2, day = 25
str_test = re.sub(r'(\d{1})-(\d{2})-(\d{4})', r'\g<3>-0\g<1>-\g<2>', '2-25-2020') # 1
print(str_test)

# year = 2020, month = 2, day = 25
str_test = re.sub(r'(\d{2})-(\d{2})-(\d{4})', r'\g<3>-\g<1>-\g<2>', '02-25-2020') # 2
print(str_test)


# year = 2020, month = 12, day = 5
str_test = re.sub(r'(\d{2})-(\d{2})-(\d{4})', r'\g<3>-\g<1>-\g<2>', '12-05-2020') # 3
print(str_test)


# year = 2020, month = 2, day = 25
str_test = re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\g<3>-\g<1>-\g<2>', '02/25/2020') # 4
print(str_test)


# year = 2020, month = 2, day = 25
str_test = re.sub(r'(\d{1})/(\d{2})/(\d{4})', r'\g<3>-0\g<1>-\g<2>', '2/25/2020') # 5
print(str_test)


# year = 2020, month = 2, day = 25
str_test = re.sub(r'(\d{4})/(\d{1})/(\d{2})', r'\g<1>-0\g<2>-\g<3>', '2020/2/25') # 6
print(str_test)


# year = 2020, month = 12, day = 5
str_test = re.sub(r'(\d{4})/(\d{2})/(\d{2})', r'\g<1>-\g<2>-\g<3>', '2020/12/05') # 7
print(str_test)

# year = 2020, month = 12, day = 5
str_test = re.sub(r'(\d{4})(\d{2})(\d{2})', r'\g<1>-\g<2>-\g<3>', '20201205') # 8
print(str_test)


-- Result
2020-02-25
2020-02-25
2020-12-05
2020-02-25
2020-02-25
2020-02-25
2020-12-05
2020-12-05

일단 위 예시는 여러 형식으로 적혀진 날짜를 모두 YYYY-MM-DD의 형식으로 고치는 코드입니다.

결과물을 보면 모두 YYYY-MM-DD이죠?

 

차례대로 살펴봅시다.

 

1번에서 먼저 각 패턴이 뭘 의미하는지를 봅시다.

r'(\d{1})-(\d{2})-(\d{4})'

먼저 (\d{2})의 의미를 알아봅시다.

\d는 0~9의 숫자를 의미합니다. {2}는 2번 반복된다는 뜻이구요.

즉, 0~9의 숫자가 2개 있는 패턴을 의미하며 이것을 괄호를 이용하여 하나의 group으로 묶었습니다.

 

(\d{1})은 0~9의 숫자(\d)가 1번 반복되는 패턴이라는 의미이며,

(\d{4})는 0~9의 숫자(\d)가 4번 반복되는 패턴이라는 의미겠죠.

 

따라서 위 패턴을 해석해보면 다음과 같습니다.

숫자1개-숫자2개-숫자4개

위 같은 패턴을 찾아서 뭐로 대체할지를 봐야겠죠.

 

 

 

r'\g<3>-0\g<1>-\g<2>'

\g라는 새로운 내용이 나왔습니다.

\g는 패턴에서 괄호로 묶어놨던 하나의 group을 의미합니다.

그리고 <> 안의 숫자는 그게 몇 번째 group인지를 의미합니다.

패턴과 위 그룹을 대응시키면 다음과같습니다.

(\d{1}) = \g<1>

(\d{2}) = \g<2>

(\d{4}) = \g<3>

 

pattern을 적을 때 괄호로 명시된 총 3개의 group은 앞에서부터 차례로 1번, 2번, 3번이 됩니다.

group이 더 많아지면 차례대로 4번 그룹, 5번 그룹, ... 이렇게되겠죠.

 

 

숫자1개-숫자2개-숫자4개

즉, 위 내용은 이러한 패턴을 찾아서

 

숫자4개-0숫자1개-숫자2개

위처럼 바꾸라는 뜻입니다.

 

뭔가 텍스트를 다른 텍스트로 대체하는게 아니라 원본 텍스트를 그룹별로 나눠 순서만 바꾼 채로 return하는것이죠.

 

(숫자1개 앞에 0이 붙은 이유는 한 자리수로 표시된 월 앞에 0을 붙여주기 위함입니다.)

 

다른 예시들도 비슷한 형식이니 천천히 읽어보며 파악해봅시다.

 

 

 

 

 

728x90
반응형