파이썬 정규 표현

maro·2024년 4월 19일

정규 표현식이란

  • 텍스트에서 특정한 형태나 규칙을 가지는 문자열을 찾기 위해 그 형태나 규칙을 정의하는 것.
  • 파이썬 뿐만 아니라 문자열을 다루는 모든 곳에서 사용된다.
  • 정규식, Regexp이라고도 한다.

기본개념

  • 패턴
    • 정규 표현식이라고 한다.
    • 문장내에서 찾기위한 문구의 형태에 대한 표현식.
  • 메타문자
    • 패턴을 기술하기 위해 사용되는 특별한 의미를 가지는 문자
    • 예) a* : a가 0회 이상 반복을 뜻한다. a, aa, aaaa
  • 리터럴
    • 표현식이 값 자체를 의미하는 것
    • 예) aa 자체를 의미한다.

정규 표현식 메타 문자

  • 패턴을 기술하기 위한 문자

문자 클래스 : [ ]

  • [ ] 사이의 문자들과 매칭
    • [abc] : a, b, c 중 하나의 문자와 매치
  • -를 이용해 범위로 설정할 수 있다.
    • [a-z] : 알파벳소문자중 하나의 문자와 매치
    • [a-zA-Z0-9] : 알파벳대소문자와 숫자 중 하나의 문자와 매치
    • [가-힣ㄱ-ㅎㅏ-ㅣ]: 한글중 하나와 매치
  • [^ 패턴] : ^ 으로 시작하는 경우 반대의 의미. 와서 안되는 패턴을 의미
    • [^abc] : a, b, c를 제외한 나머지 문자들 중 하나와 매치.
    • [^a-z] : 알파벳 소문자를 제외한 나머지 문자들 중 하나와 매치

미리 정의된 문자 클래스

  • 자주 사용되는 문자클래스를 미리 정의된 별도 표기법으로 제공한다.
  • \d : 숫자와 매치. [0-9]와 동일
  • \D : \d의 반대. 숫자가 아닌 문자와 매치. [^0-9]와 동일
  • \w : 문자와 숫자, (underscore)와 매치. `[a-zA-Z가-힣0-9]`와 동일 (문자는 특수문자 제외한 일반문자-언어상관없는-들을 말한다.
  • \W : \w의 반대. 문자와 숫자와 가 아닌 문자와 매치. `[^a-zA-Z가-힣0-9]`와 동일
  • \s : 공백문자와 매치. tab,줄바꿈,공백문자와 일치
  • \S : \s와 반대. 공백을 제외한 문자열과 매치.
  • \b : 단어 경계(word boundary) 표시. 단어와 단어를 구분할 수 있는 문자로 보통 공백이 많이 쓰임. 공백이외에도 ,, ., \n, | 등 문자들을 표현한다.
    • \b가족\b => 우리 가족 만세(O), 우리가족만세 (X)
  • \B : \b의 반대. 단어 경계로 구분된 단어가 아닌 경우
    • \B가족\B => 우리 가족 만세(X), 우리가족만세 (O)

글자수와 관련된 메타문자

  • * : 앞의 문자(패턴)과 일치하는 문자가 0개 이상인 경우. (a*b)
  • + : 앞의 문자(패턴)과 일치하는 문자가 1개이상인 경우. (a+b)
  • ? : 앞의 문자(패턴)과 일치하는 문자가 한개 있거나 없는 경우. (a?b)
  • {m} : 앞의 문자(패턴)가 m개. (a{3}b)
  • {m,} : 앞의 문자(패턴)이 m개 이상. (a{3,}b)
    • , 뒤에 공백이 들어오지 않도록 한다.
  • {m,n} : 앞의 문자(패턴)이 m개이상 n개 이하. (a{2,5}b)
  • ., *, +, ? 등 메타문자들을 리터럴로 표현할 경우 \를 붙인다.

문장의 시작과 끝 표현

  • ^ 문자열의 시작 (^abc)
    • 문자 클래스([ ])의 ^와는 의미가 다르다.
  • $ : 문자열의 끝 (abc$)

기타

  • . : 한개의 모든 문자(\n-줄바꿈 제외) (a.b)
  • | : 둘중 하나(OR) (?:010|011|016|019)
    • 010|016-111 : 010 또는 016-111 이 된다.
  • ( ) : 패턴내 하위그룹을 만들때 사용

re 모듈

  • 파이썬에서 정규 표현식을 지원하기 위한 모듈
  • 파이썬 기본 라이브러리

코딩패턴

  1. 객체지향형
    • 패턴 객체를 생성후 메소드를 호출해 원하는 처리를 한다.
         p = re.compile(r'\d+')
         p.search('abc123def')
    • re.compile() :
  2. 함수형
    • re 모듈의 원하는 작업을 하는 함수를 호출한다. Argument로 패턴과 처리할 값을 전달한다.
re.search(r'\d+', 'abc123def')

raw string

  • 패턴문자중 \로 시작하는 것들을 사용할 경우 escape 문자와의 구분을 위해 \\ 두개씩 작성해야한다. 그래서 패턴을 지정할 때는 raw string을 사용하는 것이 편리하다.
    • re.compile('\b가족\b') : \b를 escape 문자 b(백스페이스)로 인식
    • re.compile(r'\b가족\b') : \b가 일반문자가 되어 컴파일시 정규식 메타문자로 처리된다.

검색함수

  • match(), search() : 패턴과 일치하는 문장이 있는지 여부를 확인할 때 사용
  • findall(), finditer(s) : 패턴과 일치하는 문장을 찾을 때 사용

Match class

  • 검색 결과를 담는 class
    • match(), search() 의 반환타입으로 검색결과를 담는다.
  • 패턴과 일치한 문자열과 그 문자열의 위치를 가진다.
  • 주요 메소드
    • group() : 매치된 문자열들을 튜플로 반환
    • group(subgroup 번호) : 패턴에 하위그룹이 지정된 경우 특정 그룹의 문자열 반환
    • start(), end() : 대상 문자열내에서 시작, 끝 index 반환
    • span() : 대상 문자열 내에서 시작, 끝 index를 tuple로 반환

match(대상문자열 [, pos=0])

  • 대상 문자열의 시작이 정규식과 일치하는지를 조회.
  • pos : 시작 index 지정
  • 반환값
    • Match 객체: 일치하는 문자열이 있는 경우
    • None: 일치하는 문자열이 없는 경우

search(대상문자열 [, pos=0])

  • 대상문자열 전체 안에서 정규식과 일치하는 것이 있는지 조회
  • pos: 찾기 시작하는 index 지정
  • 반환값
    • Match 객체: 일치하는 문자열이 있는 경우
    • None: 일치하는 문자열이 없는 경우|

findall(대상문자열)

  • 대상문자열에서 정규식과 매칭되는 문자열들을 리스트로 반환
  • 반환값
    • 리스트(List) : 일치하는 문자열들을 가진 리스트를 반환
    • 일치하는 패턴이 없을 경우 빈 리스트를 반환한다.

finditer(대상문자열)

  • 대상문자열에서 정규식과 매칭되는 결과들을 조회할 수있는 Iterator를 반환한다.
  • 반환값
    • callable_iterator
    • 일치하는 패턴이 없어도 iterator객체는 반환되는데 next()시 StopIteration Exception발생한다.

TODO

info ='''김정수 kjs@gmail.com 801033-1010221
박영수 pys.abc@gmail.com 700121-1120212
이민영 lmy-abc@naver.com 820301-2020122
김순희 ksh@daum.net 781223-2012212
오주연 ojy@daum.net 900522-1023218
'''
# Email 주소만 추출 해서 출력
email_pattern = r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"
email_list = re.findall(email_pattern, info)
email_list

['kjs@gmail.com',
'pys.abc@gmail.com',
'lmy-abc@naver.com',
'ksh@daum.net',
'ojy@daum.net']

# 주민번호들만 조회해서 출력
jumin_pattern = r"\d{6}-?\d{7}"
jumin_pattern = r"\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12][0-9]|3[01])-[1234]\d{6}"       
# \d{2}: 년도 숫자 두자리.
# 01 ~ 09, 10, 11, 12 -  (?:0[1-9]|1[0-2])   괄호로 단순히 묶기.  0[1-9]:01~09    |  or ==> 월
# (?:0[1-9]|[12][0-9]|3[01]) : 01 ~ 09, 10~19, 20~ 29, 30, 31
## [1234]\d{6} : 0[1-9]  or  [12][0-9] or 3[01]
jumin_list = re.findall(jumin_pattern, info)
print(jumin_list)

['700121-1120212', '820301-2020122', '781223-2012212', '900522-1023218']

문자열 변경

  • sub(): 변경된 문자열 반환
  • subn(): 변경된 문자열, 변경개수 반환

sub(바꿀문자열, 대상문자열 [, count=양수])

  • 대상문자열에서 패턴과 일치하는 것을 바꿀문자열로 변경한다.
  • count: 변경할 개수를 지정. 기본: 매칭되는 문자열은 다 변경
  • 반환값: 변경된 문자열

subn(바꿀문자열, 대상문자열 [, count=양수])

  • sub()와 동일한 역할.
  • 반환값 : (변경된 문자열, 변경된문자열개수) 를 tuple로 반환
print(info)
r = re.sub(jumin_pattern, "", info)
r = re.sub(jumin_pattern, "******-*******", info)
print(r)

김정수 kjs@gmail.com 801033-1010221
박영수 pys.abc@gmail.com 700121-1120212
이민영 lmy-abc@naver.com 820301-2020122
김순희 ksh@daum.net 781223-2012212
오주연 ojy@daum.net 900522-1023218
김정수 kjs@gmail.com 801033-1010221
박영수 pys.abc@gmail.com **-***
이민영 lmy-abc@naver.com **-***
김순희 ksh@daum.net **-***
오주연 ojy@daum.net **-***

패턴내 하위패턴 만들기 (Grouping)

  • 여러 속성값들 구성된 패턴에서 각 속성의 패턴을 묶어서 하위 그룹(하위 패턴)으로 정의한다.
  • 예를 들어 날짜의 경우 년도/월/일 형식을 가진다. 여기에서 년도, 월, 일 의 패턴을 전체 날짜의 하위 패턴으로 묶어준다.
  • 구문: 하위 패턴을 소괄호로 묶어준다.
    • (\d{4})/([01]\d)/([0123]\d)
    • (년도)/(월)/(일)
import re
tel = "Tel: 010-1122-2121"  # 전화번호:  통신사/지역번호 - 국번 - 번호
pattern = r"(\d{2,3})-(\d{3,4})-(\d{4})" # \d{2,3} : 숫자 2개 이상 3개 이하
#    (1번하위그룹) - (2번하위그룹) - (3번하위그룹)
p = re.compile(pattern)
result = p.search(tel)  # 전체 패턴을 이용해서 찾는다.
if result:
    print(result)
    print("찾은 전체 문자열:", result.group(), result.group(0))
    print("1번 하위 그룹-지역/통신사번호:", result.group(1))
    print("2번 - 국번:", result.group(2))
    print("3번 - 번호:", result.group(3))
else:
    print("없음")

<re.Match object; span=(5, 18), match='010-1122-2121'>
찾은 전체 문자열: 010-1122-2121 010-1122-2121
1번 하위 그룹-지역/통신사번호: 010
2번 - 국번: 1122
3번 - 번호: 2121

emails = "abc@naver.com aaaa@daum.net sdkdkd@gmail.com"
p = re.compile(r"(\w+)@(\w+\.\w{2,4})")
e_list1 = p.findall(emails)
e_list2 = p.finditer(emails)
print(e_list1)
for email in e_list2 :
    print(email.group())
    print("게정:", email.group(1))
    print("도메인:", email.group(2))    

[('abc', 'naver.com'), ('aaaa', 'daum.net'), ('sdkdkd', 'gmail.com')]
abc@naver.com
게정: abc
도메인: naver.com
aaaa@daum.net
게정: aaaa
도메인: daum.net
sdkdkd@gmail.com
게정: sdkdkd
도메인: gmail.com

패턴 내에서 하위그룹 참조

  • \번호
  • 지정한 '번호' 번째 패턴으로 매칭된 문자열과 같은 문자열을 의미
tels = """010-2222-3232
010-3232-3232
010-5555-5555
010-4333-1212
"""
# 국번과 번호가 같은 값인 전화번호를 찾기
pattern = r"\d{2,3}-(\d{4})-\1"
#  번호-(1번하위그룹)-1번하위그룹의 문자열과 같은 문자열
## \번호 => 번호의 하위그룹과 같은 문자열
result = re.finditer(pattern, tels)
for tel in result:
    print(tel.group(), tel.group(1))     #, tel.group(2))

010-3232-3232 3232
010-5555-5555 5555

패턴내의 특정 부분만 변경

info ='''김정수 kjs@gmail.com 801033-1010221
박영수 pys.abc@gmail.com 700121-1120212
이민영 lmy-abc@naver.com 820301-2020122
김순희 ksh@daum.net 781223-2012212
오주연 ojy@daum.net 900522-1023218
'''
# 주민번호에서 뒤에 6글자를 감추기. (패턴으로 찾은 대상의 일부만 변경.)
pattern = r"(\d{6}-[1234])\d{6}"  # 변경하지 않을 부분을 subgroup화.
result = re.sub(pattern, "\g<1>######", info)
#  \g<1>  1번 subgroup의 내용을 그대로 나오게 사용. ==> 찾은 문자열의 일부를 변경시 사용.
print(result)

김정수 kjs@gmail.com 801033-1######
박영수 pys.abc@gmail.com 700121-1######
이민영 lmy-abc@naver.com 820301-2######
김순희 ksh@daum.net 781223-2######
오주연 ojy@daum.net 900522-1######

# info에서 이메일에서 계정을 #### 감추기.
p = re.compile(r"([\w\.-]+)@(\w+\.\w{2,4})")  # (g1)@(g2)
result = p.sub("####@\g<2>", info)
print(result)

김정수 ####@gmail.com 801033-1010221
박영수 ####@gmail.com 700121-1120212
이민영 ####@naver.com 820301-2020122
김순희 ####@daum.net 781223-2012212
오주연 ####@daum.net 900522-1023218

sub group으로 묶인 것 참조(조회)

  • 패턴 안에서 참조
    • \번호 , r'(\d{3}) \1' => 중복되는 것을 패턴으로 표현할 때.
  • match 조회
    • match객체.group(번호)
  • sub() 함수에서 대체 문자로 참조
    • \g<번호>

Greedy 와 Non-Greedy

  • Greedy(탐욕스러운-최대일치) 의미
    • 주어진 패턴에 만족하는 문자열을 최대한 넓게(길게) 잡아 찾는다.
    • 매칭시 기본 방식
  • Non-Greedy(최소일치)
    • 주어진 패턴에 만족하는 문자열을 최초의 일치하는 위치까지 찾는다
    • 개수를 나타내는 메타문자(수량자)에 ? 붙인다.
    • *?, +?, {m,n}?
content = "<div>파이썬<b>정규표현식</b></div>입니다."
# 태그들을 조회 <div><b></b></div>
# result = re.findall(r"<.+>", content)
result = re.findall(r"<.+?>", content)
result
['<div>', '<b>', '</b>', '</div>']
profile
공부 & 프로젝트 & 개발 블로그

0개의 댓글