본문 바로가기
  • 실행력이 모든걸 결정한다
Python

[Python 간단정리 5] 정규 표현식

by 김코더 김주역 2023. 1. 8.
반응형

1. 정규 표현식이란?

- 문자열의 패턴을 의미하며, 복잡한 문자열을 처리할 때 사용하는 기법이다.

- 문자열과 정규 표현식의 부합 여부를 따진 뒤에 그 결과로 문자열을 처리한다.

- 파이썬만의 고유 문법이 아니라 문자열을 처리하는 모든 곳에서 사용된다.

 

 

 

2. 메타 문자

(각 예시에서 문자열과 정규 표현식의 일치 여부는 MATCH ANY(정규 표현식에 일치하는 부분이 있는지)를 기준으로 판단할 것임을 앞서 밝힌다.

 

1) 문자 클래스 []

- [와 ] 사이에 있는 문자들 중 한 개의 문자와 매치

- 하이픈(-)을 사용하게 되면 두 문자 사이의 범위를 의미한다.

- ^은 반대의 의미를 갖는다.

예시 1) [abc] : a, b, c 중 한 개의 문자와 매치
a -> 일치
beaf -> 일치
dude -> 불일치

예시 2) [a-zA-Z] : 알파벳 모두
예시 3) [0-9] : 숫자
예시 4) [^0-9] : 숫자가 아닌 문자

 

 

2) Dot .

- \n을 제외한 모든 문자와 매치

예시1) a.b : a와 b 사이에 \n을 제외한 어떠한 문자가 들어가면 매치
aab -> 일치
a0b -> 일치
aba -> 불일치

예시2) a[.]b : a와 b 사이에 .가 포함되어 있으면 매치
([] 내에 .가 사용된다면 문자 . 그대로를 의미함)

 

 

3) 반복 *

- * 바로 앞에 있는 문자가 0번 이상 반복되면 매치

예시) ca*t : c+a(0번 이상 반복)+t
ct -> 일치
cat -> 일치
cet -> 불일치
caaat -> 일치

 

 

4) 반복 +

- * 바로 앞에 있는 문자가 1번 이상 반복되면 매치

예시) ca*t : c+a(1번 이상 반복)+t
ct -> 불일치
cat -> 일치
caaat -> 일치

 

 

5) 반복 {m}, {m,n}, ?

- 반복 횟수가 지정된 범위에 해당하면 일치

- {m} : 반복 횟수를 m회로 고정

예시) ca{2}t : c+a(2번 반복)+t
cat -> 불일치
caat -> 일치

 

- {m, n} : 반복 횟수가 m이상 n이하면 매치 (생략된 m 또는 n은 0을 의미)

※ {1, }은 +와 동일하고, {0, }은 *와 동일함

예시) ca{2,5}t : c+a(2~5번 반복)+t

 

- ? : {0, 1}과 동일하다.

예시) ab?c : a+b(0회 또는 1회)+c

※ ?은 *?, +?, ??, {m, n}?과 같이 사용할 수 있으며, 가능한 한 가장 최소한의 반복을 수행하도록 돕는 역할도 한다. 실제로 .*는 광역 매치, .*?는 최소 매치로 자주 쓰인다.

 

 

6) |

- or과 동일한 의미로 사용

- A|B라는 정규식이 있다면 A 또는 B라는 의미다.

예시) [a-z]|[^A-Z] : [a-z] 또는 [^A-Z]
A -> 불일치
_ -> 일치
a -> 일치

 

 

7) ^

- 문자열의 맨 처음과 일치

예시) ^python : 문자열이 'python'으로 시작하면 매치
python is easy -> 일치
hi python -> 불일치
pytho n -> 불일치

 

 

8) $

- 문자열의 맨 끝과 일치

예시) python$ : 문자열이 'python'으로 끝나면 매치
python is easy -> 불일치
hi python -> 일치
pytho n -> 불일치

 

 

9) 그룹 ()

- 여러 식을 하나로 묶을 때 사용

예시) (ABC)+
ABC -> 일치
AB -> 불일치
ABCCC -> 일치

- 잠시 후 [4-3) match 객체의 메소드]에서 살펴볼 group 메소드를 통해 그룹핑된 부분의 문자열만 뽑아낼 수도 있다.

 

 

 

3. 특수 기호

(각 예시에서 문자열과 정규 표현식의 일치 여부는 MATCH ANY(정규 표현식에 일치하는 부분이 있는지)를 기준으로 판단할 것임을 앞서 밝힌다.

 

- \d : [0-9]와 동일

- \D : [^0-9]와 동일

- \s : [ \t\n\r\f\v]와 동일

- \S : [^ \t\n\r\f\v]와 동일

- \w : 알파벳, 숫자, _ (파이썬에서는 한글도 포함) 중의 한 문자

예시) \w
_ _ _ -> 일치
! -> 불일치
김주역 -> 일치
awa -> 일치

- \W : 알파벳, 숫자, _ (파이썬에서는 한글도 포함) 이 아닌 한 문자

예시) \W
_ _ _ -> 일치
! -> 일치
김주역 -> 불일치
awa -> 불일치

- \A : 라인과 상관없이 문자열의 맨 처음와 일치 (MULTILINE, M 옵션 적용 여부와 무관)

- \Z : 라인과 상관없이 문자열의 맨 끝과 일치 (MULTILINE, M 옵션 적용 여부와 무관)

- \b : 단어 구분자로 구분된 단어인 경우에 매치

예시) \bmelon\b : 앞뒤가 단어구분자로 구분된 'melon'이라는 단어와 매치
watermelon -> 불일치
I love melon -> 일치
I ate a lot of melons -> 불일치

- /B : 단어 구분자로 구분된 단어가 아닌 경우에 매치

 

 

 

4. 정규 표현식의 활용 : re 모듈

- re 모듈은 파이썬이 설치될 때 자동으로 설치되는 기본 라이브러리다.

 

1) 패턴 객체 생성

import re
p=re.compile('[a-z]+') # 패턴 객체 p를 이용하여 그 이후의 작업 수행

 

 

2) 패턴 객체가 제공하는 문자열 검색 메소드

- match : 문자열의 처음부터 정규식과 매치되는지 조사해서 매치되면 해당 match 객체를 반환하고, 그렇지 않다면 None을 반환한다.

- search : 문자열 전체를 검색하여 정규식과 매치되는 첫 번째 위치만 찾고, 대응하는 match 객체를 반환한다. 문자열의 어느 위치도 패턴과 일치하지 않다면 None을 반환한다.

import re

p=re.compile('[a-z]+')
print(p.match('python')) # <re.Match object; span=(0, 6), match='python'>
print(p.match(' python')) # None
print(p.match('top1 python')) # <re.Match object; span=(0, 3), match='top'>
print(p.search('python')) # <re.Match object; span=(0, 6), match='python'>
print(p.search(' python')) # <re.Match object; span=(1, 7), match='python'>
print(p.search('top1 python')) # <re.Match object; span=(0, 3), match='top'>

- findall : 정규식과 매치되는 모든 문자열(substring)을 리스트로 반환

- finditer : 정규식과의 매치 결과를 나타내는 match 객체를 모두 모아 반복 가능한 객체로 반환

import re

p=re.compile('[a-z]+')
print(p.findall('top1 python')) # ['top', 'python']

print(p.finditer('top1 python')) # <callable_iterator object at 0x0000028299D4A8C0>
for word in p.finditer('top1 python'):
    print(word) # 각각 <re.Match object; span=(0, 3), match='top'>, <re.Match object; span=(5, 11), match='python'> 출력

- sub : 정규식과 매치되는 부분을 다른 문자열로 대체한 결과를 반환한다. 첫 번째 인자로 대체 문자열을 입력하고, 두 번째 인자로 대상 문자열을 입력하고, 세 번째 인자로 최대 대체 횟수를 입력하면 된다(생략 가능).

import re

p=re.compile('(바보|쓰레기)')
print(p.sub('xxx', '이런 쓰레기같은 놈이 다있나! 바보 녀석', count=2)) # 이런 xxx같은 놈이 다있나! xxx 녀석

※ 참고로, 대체 문자열에 \g<인덱스> 또는 \g<이름>을 포함하는 식으로 정규식의 그룹 참조 구문을 사용할 수도 있다. 그룹 참조 구문에 대한 내용은 [3) match 객체의 메소드]의 group 메소드에서 설명할 것이다.

아래와 같이 sub 메소드의 첫 번째 인자로 함수를 넣는 방법도 있다. 해당 함수는 match 객체를 인자로 받아 대체 문자열을 리턴해야 한다.

import re

def repl(match): # 비속어의 글자수에 맞게 x를 출력
    n=len(match.group())
    return 'x'*n
    
p=re.compile('(바보|쓰레기)')
print(p.sub(repl, '이런 쓰레기같은 놈이 다있나! 바보 녀석', count=2)) # 이런 xxx같은 놈이 다있나! xx 녀석

- subn : sub와 비슷한 역할을 하지만, 변경된 문자열과 대체 횟수를 튜플로 반환한다는 차이가 있다.

import re

p=re.compile('(바보|쓰레기)')
print(p.subn('xxx', '이런 쓰레기같은 놈이 다있나! 바보 녀석', count=2)) # ('이런 xxx같은 놈이 다있나! xxx 녀석', 2)

 

 

3) match 객체의 메소드

- group : 매치된 문자열을 반환

- start : 매치된 문자열의 시작 위치를 반환

- end : 매치된 문자열의 끝 위치를 반환

- span : 매치된 문자열의 (시작, 끝)에 해당되는 튜플을 반환

import re

p=re.compile('[a-z]+')
print(p.search(' python')) # <re.Match object; span=(1, 7), match='python'>
print(p.search(' python').group()) # 'python'
print(p.search(' python').start()) # 1
print(p.search(' python').end()) # 7
print(p.search(' python').span()) # (1, 7)

group()과 group(0)은 매치된 전체 문자열을 반환하고, group(n)은 n번째 그룹에 해당되는 문자열을 반환한다. 그룹이 중첩되어 있는 경우는 바깥쪽 그룹부터 시작하여 안쪽 그룹으로 들어갈수록 인덱스가 증가한다.

import re

p=re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)") # 이름 + " " + 전화번호
m=p.search('Jooyeok 010-7345-2080')
print(m.group(0)) # Jooyeok 010-7345-2080
print(m.group(1)) # Jooyeok
print(m.group(2)) # 010-7345-2080
print(m.group(3)) # 010

※ 정규식 앞에 'r'을 붙이는 이유는 [6) 백슬래시 문제]를 참고하자.

참고로, 정규식 내에서 그룹핑된 문자열을 재참조할 수도 있다. 참조 방식으로는 인덱스 참조 방식과 이름 참조 방식이 있다.

import re

# 인덱스 참조 방식
# 인덱스 참조 : \인덱스 
p=re.compile(r'(\b\w+)\s+\1')
m=p.search('Jooyeok Jooyeok')
print(m.group(0)) # Jooyeok Jooyeok
print(m.group(1)) # Jooyeok

# 이름 참조 방식
# 이름 정의 : (?P<이름>정규식)
# 이름 참조 : (?P=이름)
p=re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
m=p.search('Jooyeok Jooyeok')
print(m.group(0)) # Jooyeok Jooyeok
print(m.group('word')) # Jooyeok

 

 

4) 패턴 객체 정의 생략하기

- re.compile 메소드를 사용하지 않고 축약한 형태로 사용하는 방법이다. 한 번 만든 패턴을 여러 번 사용해야 한다면 re.compile을 사용하는 것이 유리하다.

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

 

 

5) 컴파일 옵션

- DOTALL, S : 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 설정

p=re.compile('a.b', re.DOTALL) # 또는 re.compile('a.b', re.S)

- IGNORECASE, I : 대·소문자에 관계 없이 매치할 수 있도록 설정

- MULTILINE, M : 문자열 전체의 처음이 아닌 각 라인의 처음과 매치할 수 있도록 설정 (^, $ 메타 문자와 같이 활용됨)

import re

data='python one\nlife is too short\npython two\nyou need python'

p=re.compile("^python\s\w+")
print(p.search(data)) # <re.Match object; span=(0, 10), match='python one'>
print(p.findall(data)) # ['python one']

p=re.compile("^python\s\w+", re.MULTILINE)
print(p.search(data)) # <re.Match object; span=(0, 10), match='python one'>
print(p.findall(data)) # ['python one', 'python two']

- VERBOSE, X : 이해하기 어려운 정규식을 라인 단위로 구분하거나 주석과 함께 작성할 수 있도록 설정한다. 정규식 내에 사용된 whitespace와 주석문은 컴파일 시 제거된다. 단, [] 내에 사용된 whiltespace는 제거되지 않는다.

 

 

6) 백슬래시 문제

- 백슬래시 문제 : [3. 특수 기호] 에서 언급한 것들이 백슬래시 문제에 처할 수 있다. 정규식에서 사용한 \ 문자가 문자 그 자체임을 알려주려면 \을 연속으로 2번 사용(\\)하여 이스케이프 처리를 해야 한다. 그런데, 파이썬 정규식 엔진에서는 \\이 |로 변경되는 문제가 생기기 때문에 정규식 엔진에 \\ 문자를 전달하려면 파이썬은 결국 \\\\을 전달해야 한다. 이렇게 많은 백슬래시를 쓰는건 가독성에 좋지 않다.

- 문자열에 포함되어 있는 백슬래시를 문자 그 자체로써 사용할 수 있게 하기 위해 파이썬에는 Raw String이라는 규칙이 생겨났다. Raw String 규칙을 활용하기 위해 문자열 앞에 r 문자를 삽입하면 된다.

- 예를 들어, '\section' 이라는 문자열을 찾기 위한 패턴 객체는 다음과 같이 생성한다.

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

 

 

 

5. 전방 탐색

1) 긍정형 전방 탐색

- 형식 : (?=정규식)

- 정규식과 매치되어야 함

- 검색에는 포함되지만 검색 결과에는 제외됨

import re

p=re.compile(".+:")
m=p.search("http://google.com")
print(m.group(0)) # http:

p=re.compile(".+(?=:)")
m=p.search("http://google.com")
print(m.group(0)) # http

 

 

2) 부정형 전방 탐색

- 형식 : (?!정규식)

- 정규식과 매치되지 않아야 함

- 검색에는 포함되지만 검색 결과에는 제외됨

import re

p=re.compile(".*[.](?!bat$).*$") # 확장자가 .bat이 아닌 파일
print(p.search("hello.exe")) # <re.Match object; span=(0, 9), match='hello.exe'>
print(p.search("foo.bat")) # None
print(p.search("main.html")) # <re.Match object; span=(0, 9), match='main.html'>

 

 

 

6. 자주 사용하는 정규 표현식

- 숫자 : ^[0-9]*$

- 영문자 : ^[a-zA-Z]*$

- 한글 : ^[가-힣]*$

- 이메일 : \\w+@\\w+\\.\\w+(\\.\\w+)?

- 전화번호 : ^\d{2,3}-\d{3,4}-\d{4}$

- 휴대전화번호 : ^01(?:0|1|[6-9])-(?:\d{3}|\d{4})-\d{4}$

- 주민등록번호 : \d{6} \- [1-4]\d{6}

- 우편번호 : ^\d{3}-\d{2}$

※ 출처 : https://coding-factory.tistory.com/819

 

 

 

 

● 참고 자료 : Do it! 점프 투 파이썬

 

 

반응형

댓글