ㅅㅇ
python 프로그래밍 : 정규표현식 본문
_플레이데이터 빅데이터캠프 공부 내용
1. 정규 표현식(Regular Expression) 개요
1.1. 정규 표현식이란
- 텍스트에서 특정한 형태나 규칙을 가지는 문자열을 찾기 위해 그 형태나 규칙을 정의하는 것.
- 텍스트 데이터 내 부분적인 데이터를 처리하기 위한 방법.
- 파이썬 뿐만 아니라 문자열을 다루는 모든 곳에서 사용된다.
- 정규식, Regexp이라고도 한다.
# 데이터 내 부분적인 데이터를 처리하기 위한 방법 => 정규 표현식
# 예를 들다면, 국번을 모두 모두 #으로 가리기.
#전화번호를 입력받는데 이 번호에 특정 번호가 있는지를 알고 싶다.
'''
010-1111-2222
010-234-3432
011-3333-5555''' #문자열 데이터 내 부분적으로 처리를 원한다.
# 그리고 사실 정규표현식 처리를 위해서는 일정한 패턴, 패턴 규칙의 데이터이여야 한다.
** regexr.com
정규표현식을 다 외우고 또 규칙 형태 찾고 적용하고 쉽지 않기에 구글링해서 찾고 상황에 맞게 내가 바꾸면 됨.
정규표현식 공부는 무작정 외우기 보다는 예제를 가지고 익히는 게 좋다.
** 공백 조심하기. 공백도 정규표현식을 표현하는 리터널이다.
** 패턴을 찾을 때 늘 처음 문자부터 왼쪽에서 오른쪽으로 비교한다. greedy
1.2. 기본개념
- 패턴
- 정규 표현식이라고 한다.
- 문장내에서 찾기위한 문구의 형태에 대한 표현식.
- 메타(meta)문자와 리터럴(literal) 로 구성한다.
- 메타문자
- 패턴을 기술하기 위해 사용되는 특별한 의미를 가지는 문자(무언가를 표현하기 위해)
- 예) `a*` : a가 0회 이상 반복을 뜻한다. : a, aa, aaaa : a가 여러 글자 올 수 있다.
- \d, \w ...
- 리터럴
- 표현식이 값 자체를 의미하는 것
- 내가 찾는 값
- 예) `a`는 `a` 자체를 의미한다
2. 정규 표현식 메타 문자
- 패턴을 기술하기 위한 문자
2.1 문자 클래스 : [ ] 대괄호 안에 넣는
- `[ ]` 사이의 문자들과 매칭
- `[abc]` : a, b, c 중 **하나의 문자(이들중 하나)**와 매치
- `-`를 이용해 범위로 설정할 수 있다.(순서가있다면)
- `[a-z]` : 알파벳소문자중 하나의 문자와 매치
- `[a-zA-Z0-9]` : 알파벳대소문자와 숫자 중 하나의 문자와 매치
- '[ㄱ-ㅎ]','[가-힣]': 모든한글을표현
- `[^ 패턴]` : ^ 으로 시작하는 경우 반대의 의미(대괄호 안에 있다는 것 주의)
- `[^abc]` : a, b, c를 제외한 나머지 문자들 중 하나와 매치.
- `[^a-z]` : 알파벳 소문자를 제외한 나머지 문자들 중 하나와 매치
2.2 미리 정의된 문자 클래스
- 자주 사용되는 문자클래스를 미리 정의된 별도 표기법으로 제공한다.
- 한글자단위. 한글자를 찾는 것.(여러글자라면 아래 2.3와 같은 메타문자와 함께 써주기!)
- 대문자와 소문자는 반대의 의미를 가지고 있다.
- `\d` : 숫자와 매치. [0-9] 와 동일
- `\D` : `\d`의 반대. 숫자가 아닌 문자와 매치. [^0-9] 와 동일
- `\w`: 영문자(대소)와 숫자, _(underscore)와 매치. `[a-zA-Z0-9_가-힣]` 와 동일
- `\W` : `\w`의 반대. 문자와 숫자와 _ 가 아닌 문자와 매치. `[^a-zA-Z0-9_]` 와 동일
- `\s` : 공백문자와 매치. tab, 줄바꿈, 공백문자와 일치
- `\S` : `\s`와 반대. 공백을 제외한 문자열과 매치.
- `\b` : 단어 경계(word boundary) 표시. 보통 단어 경계로 공백을 의미. 양쪽 공백으로 떨어져 있는 단어
- `\b가족\b` => 우리 가족 만세(O), 우리가족만세 (X)
- 공백 없앨 때 sub()함수와 함께
- `\B` : `\b`의 반대. 단어 경계로 구분된 단어가 아닌 경우
- `\B가족\B` => 우리 가족 만세(X), 우리가족만세 (O)
2.3. 글자수와 관련된 메타문자
- `.` : 한개의 모든 문자 (\n-줄바꿈 제외) (`a.b`)
- `a.b` a와 b 사이에 뭐가 되었든 한 글자면! 근데 줄바꿈은 안된다. 엔터는 다른 문장을 의미하게 됨.
- 앞 뒤 글자가 무엇이든 그냥 . 부분에 한 글자가 들어오면 해당.
- 이 아래부터는 앞에 있는 문자에 대한 패턴을 기술하기 위해 특별한 의미를 가지고 있는 메타문자이다.
- `*` : 앞의 문자(패턴)과 일치하는 문자가 0개 이상인 경우. (`a*b`) aaab aab ab b
- `+` : 앞의 문자(패턴)과 일치하는 문자가 1개이상인 경우. (`a+b`) aaab aab ab
- `?` : 앞의 문자(패턴)과 일치하는 문자가 한개 있거나 없는 경우. (`a?b`) ab, b
- 만약 문장에 aaaab가 포함되어있다면 aaaab에서 ab 패터내을 찾을 것이다.?
- `{m}` : 앞의 문자(패턴)가 m개. (`a{3}b`) a 3개 aaab
- `{m,}` : 앞의 문자(패턴)이 m개 이상. (`a{3,}b`) a가 3개 이상 aaab aaaab
- , 뒤에 공백이 들어오지 않도록 한다.
- `{m,n}` : 앞의 문자(패턴)이 m개이상 n개 이하. (`a{2,5}b`) 2개이상 5개 이하
- 사이에 공백 있으면 안된다.
- `.`, `*`, `+`, `?` : 앞의 문자(패턴)이 리터럴로 표현할 경우 `\`를 붙인다.
- ex) 합니까? 를 찾고 싶을 때? ==? `합니까?\?'
- [] 문자클래스 안에 있으면 안붙여도 알아서 리터널로 인식한다.
2.4. 문장의 시작과 끝 표현
- `^` 문자열의 시작 (`^abc`)
- 문자 클래스([ ])의 ^와는 의미가 다르다.
- `$` : 문자열의 끝 (`abc$`)
2.5. 기타
- `|` : 둘중 하나 (OR) (010|011|016|019) 010 이랑 011이랑..
- ( )로 묶어서 사용한다. |구분 단위로
- `[]` 안에 넣으면 0 하나 1하나 한 글자 씩 따로 보게 됨
- 010|016-111 : 010 또는 016-111 이 된다.
- `( )` : 패턴내 하위그룹을 만들때 사용
3. re 모듈
- 파이썬에서 정규 표현식을 지원하기 위한 모듈
- 파이썬 기본 라이브러
3.1 코딩패턴
raw string(r-string)
- 패턴을 지정하는 문자열 앞에 r 표시구분자를 붙인 것을 말한다.
- `\`를 일반문자로 처리하기 위해서 사용.
(\은 escape, 메타문자 두 가지 기능이 있기에 구분되야 한다.)
- `re.compile('\b가족\b')` : `\b`를 escape 문자 b(백스페이스)로 인식
- `re.compile(r'\b가족\b')` : `\b`가 일반문자가 되어 컴파일시 정규식 메타문자로 처리된다.
print('가족\b') #백스페이스
print(r'가족\b') # 이렇게 넣어야 정규표현식으로 쓸 수 있다.
# \ 안써도 오해가 소지가 없더라도 무조건 r써주기
p = r'12[a-z]3902'
가
가족\b
.1단계. 모듈 import re
.2단계. 패턴 객체 생성
- 패턴 컴파일
- 패턴을 가지고 있는 객체 생성
1) 패턴생성
2) 처리함수
- 호출방식의 차이 - 1 객체지향 2 함수형
<정규식 관련 함수 구문의 두형태>
방식 1) pattern객체.메소드(매개변수)
```python
p = re.compile(r'\d+') #패턴생성. attribute로 패턴을 가지고 있는 객체 self안에 패턴
p.search('abc123def') # 대상 'abc123def'
```
방식 2) re.정규식사용함수(패턴, 매개변수)
```python
re.search(r'\d+', 'abc123def') # 함수 안에 패턴과 매개변수를 넣어
```
# 첫번째 매개변수: 패턴, 두번째 매개변수 : 찾으려는 대상문자열, 세번째부터 : 기타설정
# 둘 다 많이 씀.
.3단계. 텍스트(대상)에서 패턴 문자열 검색또는 변경 작업
<3.2 검색함수>
- match(), search() : 패턴과 일치하는 문장이 있는지 여부를 확인할 때 사용 ==> group() ... 메소드 사용
- findall() : 패턴과 일치하는 문장을 찾을 때 사용
- finditer() : iterator를 반환. next() 결과를 Match객체로 반환
3.2.1. Match 객체
- 검색 결과를 담아 반환되는 객체
- match(), search() 의 반환타입
- 패턴과 일치한 문자열과 대상문자열 내에서의 위치를 가지고 있는 객체
- findall() findintr()는 없다.
- 주요 메소드
- group() : 매치된 문자열들을 튜플로 반환
- group(subgroup 번호) : 패턴에 하위그룹이 지정된 경우 특정 그룹의 문자열 반환
- start(), end() : 찾는 대상이 문자열내에서의 시작, 끝 index 반환
- span() : 대상 문자열 내에서 시작, 끝 index를 tuple로 반환
3.2.2 match(대상문자열 [, pos=0])
- 대상 문자열의 시작 부터 정규식과 일치하는 것이 있는지 조회
- pos : 시작 index 지정
- 반환값
- Match 객체 : 일치하는 문자열이 있는 경우
패턴에 맞는 것을 찾으면 찾은 정보를 Match 객체에 담아 반환
- None: 일치하는 문자열이 없는 경우
- search 첫번째로 찾은 것 하나만! 반환. 그 뒤에 있어도.
뒤에 꺼를 원하면 시작 인덱스를 설정해줘야 한다.
- 첫번째 매개변수 패턴, 두번째 매개변수 대상문자열, 세번째 매개변수 기타설정
- def match(self,--) self 객체에 패턴이 들어가는 것이다.
1.
# EX. 함수형 방식_정수 한글자 찾기_ 검색함수 match()
import re
txt = "안녕하세요. 제 나이는 20세 입니다."
pattern_txt = r"\d+" # \d : 정수하나 +:1글자 이상 => 정수 한글자 이상
m = re.match(pattern_txt, txt) #match()함수 : 대상문자열이 패턴으로 시작하냐?
print(m)
None
2.
# EX. 함수형 방식- 문자, 숫자 , _ 중 1글자 이상m - 검색함수 re.match - m.group
import re
txt = "안녕하세요. 제 나이는 20세 입니다."
pattern_txt = r"\w+" # \w :문자,숫자,_ 중 +:1글자 이상
m = re.match(pattern_txt, txt)
print(m)
print("찾는 문자열:", m.group())
print("찾는 문자열의 시작 index:", m.start())
print("찾는 문자열의 끝 index:", m.end()) # end index는 포함 안 됨
print("시작, 끝 index :", m.span())
<re.Match object; span=(0, 5), match='안녕하세요'>
찾는 문자열: 안녕하세요
찾는 문자열의 시작 index: 0
찾는 문자열의 끝 index: 5 # 마지막 index 제외
시작, 끝 index : (0, 5)
- 처음부터 찾아서 패턴에 포함되지 않는 글자 전까지 출력
3.
# 함수형 방식 _패턴에 메타변수 +
# 수량자 + 안 넣으면 수량을 표시 안 해준 것.
# ==> '안' 한글자만 반환함.
import re
txt = "안녕하세요. 제 나이는 20세 입니다."
print(re.match(r'\w', txt))
<re.Match object; span=(0, 1), match='안'>
4. 객체지향 방식
- re.compile() 패턴객체 생성 -> 조회/변경하는 패턴객체의 메소드를 호출
==> p의 패턴이 self로 들어가고 그 다음 매개변수에 대상 txt
# 객체지향 방식
import re
txt = "안녕하세요. 제 나이는 20세 입니다."
# 패턴객체
p = re.compile(r"\d+")
# 조회/변경하는 패턴객체의 메소드를 호출
m = p.match(txt) #p의 패턴이 self로 들어가고 그 다음 매개변수에 대상 txt
if m: # m!= None -> 조회결과가 있으면
print(m.group(), m.span())
else:
print("대상이 없음.") # 숫자 없으면
[찾는 함수들]
3.2.3 search(대상문자열 [, pos=0])
- 대상문자열 전체 안에서 정규식과 일치하는 것이 있는지 조회
- pos: 찾기 시작하는 index 지정
- 반환값
- Match 객체: 일치하는 문자열이 있는 경우
- None: 일치하는 문자열이 없는 경우
- search 첫번째로 찾은 것 하나만! 반환. 그 뒤에 있어도. 뒤에 꺼를 원하면 시작 인덱스를 설정해줘야 한다.
1.
import re
txt = "안녕하세요. 제 나이는 20세 입니다. 10년 후에는 30세가 도비니다."
p = re.compile(r"\d+")
#m = p.search(txt) # search 첫번째로 찾은 것 하나만! 반환. 그 뒤에 있어도.
m = p.search(txt, 16) # 16번 index부터 찾아 하나만 반환. -> 10년
if m:
print(m.group(), m.span()) # 찾은 해당문자열, 찾은 해당문자열의 인덱스 튜플로
else:
print("숫자가 없습니다.")
# search 첫번째로 찾은 것을 반환. 그 뒤에 있어도.
# 튜플 마지막 인덱스 미포함 (13, 15) => 13 14번째
>> 10 (22, 24)
2. 인덱스 설정해 원하는 값 찾기. 정확한 값을 찾는 정규표현식!!
# EX. 300원 말고 700원 찾기 - 인덱스 설정해 원하는 값 찾기 - 정확한 값을 찾는 정규표현식!!
txt = "가격은 300원 5000원 10000원 20000원 700원 입니다. 한번에 100개씩 주문해 주세요."
p_txt=r"\b\d{3}원" #백원대가격 : "앞에 공백한칸 정수 세개 뒤에 원"
p = re.compile(p_txt) # 객체
m = p.search(txt,7)
if m :
print(m.group(), m.span())
else:
print("없음.")
>> 700원 (29, 33)
3.2.4. findall(대상문자열)
- 대상문자열에서 정규식과 매칭되는 문자열들을 리스트로 반환
- 일치하는 애들 싹 다 리스트에 넣어 반환 (위에는 앞부터 비교해 일치하는 하나만 반환)
- 반환값
- 리스트(List) : 일치하는 문자열들을 가진 리스트를 반환
- 일치하는 문자열이 없는 경우 빈 리스트 반환 []
1. search 와 findall 비교
# 소문자 abcd...xyz 찾아 리스트에 담기
txt = 'Life is short. You need python.'
# 앞에서부터 하나만
result = re.search(r"[a-z]", txt)
# [a-z] : 모든 소문자 한 글자가 패턴 => 한글자씩 빠져 리스트에
result2 = re.findall(r"[a-z]", txt)
print(result, result.group())
print(result2)
>> <re.Match object; span=(1, 2), match='i'> i
>> ['i', 'f', 'e', 'i', 's', 's', 'h', 'o', 'r', 't', 'o', 'u', 'n', 'e', 'e', 'd', 'p', 'y', 't', 'h', 'o', 'n']
2.
# 영단어(대소문자) 찾아 리스트에 담기
txt = 'Life is short. You need python.'
# [a-z] : 모든 소문자, +:1글자 이상(공백이나 . 나 패턴에 들어가지 않는다 그럼 공백과 . 전까지 한 묶음)
result = re.findall(r"[a-zA-Z]+", txt)
print(result)
>> ['Life', 'is', 'short', 'You', 'need', 'python']
3.
# 영문대소문자, 모든 한글 글자 찾기
# 지금 파이썬3에서 3가 빠진다. 숫자는 패턴에 포함되지 않는다.
txt = 'Life is short. You need python. 파이썬3 많이 좋습A니다.'
result = re.findall(r"[a-zA-Z가-힣]+", txt)
print(result)
>> ['Life', 'is', 'short', 'You', 'need', 'python', '파이썬', '많이', '좋습A니다']
4. [a-zA-Z가-힣0-9_] 대신해서 \w
# 영문대소문자, 모든 한글 글자, 숫자, _ 찾기 => []대신해서 \w
# 파이썬3에서 3까지 출력
txt = 'Life is short. You need python. 파이썬3 많이_많이 좋습A니다.'
result = re.findall(r"[a-zA-Z가-힣0-9_]+", txt)
result2 = re.findall(r"\w+", txt)
# findall() => 결과 문자열을 리스트에 담아서 반환.(값만 주고 문장 어디에 있는 지 모름.)
print(result)
print(result2)
# 같은 처리
>> ['Life', 'is', 'short', 'You', 'need', 'python', '파이썬3', '많이_많이', '좋습A니다']
>> ['Life', 'is', 'short', 'You', 'need', 'python', '파이썬3', '많이_많이', '좋습A니다']
5. [^] 반대
# 영문대소문자, 모든 한글 글자, 숫자, _ 의 반대 찾기 => ^
# 공백 , 을 찾음
txt = 'Life is short. You need python. 파이썬3 많이_많이 좋습A니다.'
result = re.findall(r"[^a-zA-Z가-힣0-9_]+", txt)
print(result)
>> [' ', ' ', '. ', ' ', ' ', '. ', ' ', ' ', '.']
3.2.5. finditer(r"", txt)
- iterator를 반환. next() 결과를 Match객체로 반환
txt = 'Life is short. You need python. 파이썬3 많이_많이 좋습A니다.'
result_iter = re.finditer(r"\w+", txt)
print(type(result_iter), next(result_iter))
print("-"*30)
for m in result_iter:
print(m.group(), m.span())
>> <class 'callable_iterator'> <re.Match object; span=(0, 4), match='Life'>
>> ------------------------------
>> is (5, 7)
>> short (8, 13)
>> You (15, 18)
>> need (19, 23)
>> python (24, 30)
>> 파이썬3 (32, 36)
>> 많이_많이 (37, 42)
>> 좋습A니다 (43, 48)
3.3. 문자열 변경
- sub(): 변경된 문자열 반환. 보통 이거 씀.
- subn(): 변경된 문자열, 변경개수 반환. n이 갯수
3.3.1 sub(바꿀문자열, 대상문자열 [, count=양수])
- 대상문자열에서 패턴과 일치하는 것을 바꿀문자열로 변경한다.
- count: 변경할 개수를 지정. 기본: 매칭되는 문자열은 다 변경
- 반환값: 변경된 문자열
패턴 - 바꿀문자열 - 대상문자열
3.3.2 subn(바꿀문자열, 대상문자열 [, count=양수])
- sub()와 동일한 역할.
- 반환값 : (변경된 문자열, 변경된문자열개수) 를 tuple로 반환
1. sub()
# 띄여 쓰기 여러개를 한개로 변경.
import re
txt= "오늘 밥을 먹었다." # "오늘 밥을 먹었다."
p = re.compile(r"\s+") # 바꿀 대상을 패턴. \s: 공백(스페이스, 탭, 엔터) 하나! *:한 개 이상
result = p.sub(" ",txt)
print(result)
print(txt) # 원본은 그대로이다.
>> 오늘 밥을 먹었다.
>> 오늘 밥을 먹었다.
# 띄여 쓰기 여러개를 한개로 변경.
# 띄어쓰기 2칸 이상부터 찾아서 바꾸기 # {2,}로 수량을 정확히 표시해줌.
result = re.sub(r"\s{2,}", " ", txt)
# 이렇게 써도 됨. \s{2,} : 공백 두개 이상이 붙은 문자열
2. subn()
# subn()
txt= "오늘 밥을 먹었다."
p = re.compile(r"\s+")
result = p.subn(' ', txt)
print(type(result)) # 튜플
print(result) # 문자열과 숫자가 튜플에 담겨있다.
print(result[0]) # 비뀐 문자열
print(result[1]) # 변경된 문자열 갯수
>> <class 'tuple'>
>> ('오늘 밥을 먹었다.', 2)
>> 오늘 밥을 먹었다.
>> 2
3. 패턴이 일정하지 않다. 이럴땐 패턴을 찾는 것이 아니라 남길 것을 생각하자
# EX. 전화번호에서 사용된 구분자를 제거. 각 전화번호를 구분하는 공백은 남긴다.
# 패턴이 일정하지 않다. 이럴땐 패턴을 찾는 것이 아니라 남길 것을 생각하자
tel = '010-1111-2222, 01033213201 (010)3213-3031'
p = re.compile(r"[^0-9 ]") # 남길 것 : 숫자0-9랑 공백 에서 ^로
# [9 ] 대괄호 안에 공백 하나가 들어있는 것이다. 안 보인다고 착각하면 안됨!
result = p.sub("",tel)
print(result)
3.4 나누기(토큰화)
3.4.1 split(대상문자열)
- pattern의 구분자로 문장을 나눈다.
- 반환 : 나눈 문자열을 원소로 하는 리스트
# [참고 _ 문자열 쪼개기]
f ="홍길동과 김명수과 유관순"
v = f.split("과") # 나눠진 값 하나하나를 Token
v
>> ['홍길동', ' 김명수', ' 유관순']
1. 구분자들을 기준으로 쪼개서 리스트에 넣기
# . :1글자를 뜻함 근데 리터널로 표현하려며 \. 이렇게 써야 함
# 근데 문자클래스[]에서는 메타문자가 적용이 안된다.
# (메타문자가 literal로 사용됨)
txt = 'A.B|C.D,E:F' # 구분자 . | , : => 여러가지가 사용됨.
p = re.compile(r"[.|r:,]") # 구분자 패턴 객체 생성 # 메타글자
result = p.split(txt)
result
>> ['A', 'B', 'C', 'D', 'E', 'F']
- 위랑 비교
# \. 여기서는 . 을 리터널로 쓰기위해서 \을 붙여줘야 한다.
# 안 붙이면 메타변수로 쓰이면서 저 자리에는 아무 글자 하나만 들어와도 된다는 뜻으로 쓰임
http://문자열.문자열들.com
p_txt = r"http[s]*://\w+\."
# 메타문자 * : 올수도 있고 아닐수도 있고 (0 이상)
4. 그룹핑(Grouping)
- 패턴 내에서 하위패턴을 만드는 것.
- 전체 패턴에서 일부 패턴을 묶어준다.
- 구문: (하위패턴) # 괄호 쳐주기
[하위 그룹 패턴을 참조 정리]
(1) 하위그룹 index : 1부터 시작
(2) Match.group() 함수에서 그룹을 지정
- group(그룹index)
- ex) m.group(), m.group(1), m.group(2)
(3) 패턴 내에서 참조
- 특정 그룹을 찾은 문자열과 같은 문자열이 있는 위치를 지정
- \그룹index
-r"(\d{4})-\1" #1234-1234
(4) sub() 함수에서 활용 가능
- 특정 그룹에서 찾은 문자열을 이용해서 대체문자열을 생성
- \g<그룹index>
- re.sub("(\w+)-\d+", "\g<1>-@@@@@", txt)
4.1. 그룹핑 예
4.1.1 전체 패턴 내에서 일부 패턴을 조회
1. 전화번호에서 국번만 조회하려는 경우
# 전화번호 패턴 안에 국번이라는 하위패턴을 뽑아내야 한다.
# 단순히 국번(단순히 -세네숫자- )의 패턴을 찾는 게 아니라 전화번호 패턴안에서 국번에 패턴을 지정해야 함.
- 단일번호 하나에서 찾는 거라서 search() 함수로 가능
tel = '010-1111-2345'
# 전화번호(전체 패턴)에서 국번(일부패턴) 패턴
p = re.compile(r"\d{2,3}-(\d{3,4})-\d{4}")
m = p.search(tel)
print("전화번호:",m.group())
# match.group() == match.group(0) => 전체 패턴과 일치한 문자열이 0번째 인덱스
print("국번:",m.group(1))
# match.group(index1부터) => 전체패턴 내의 하위패턴과 일치한 문자열들이 1번째 인덱스부터~~
>> 전화번호: 010-1111-2345
>> 국번: 1111
- match.group() match.group(0)
=> 전체 패턴과 일치한 문자열이 0번째 인덱스
- match.group(index1부터)
=> 전체패턴 내의 하위패턴과 일치한 문자열들이 1번째 인덱스부터~~
2. 전화번호에서 지역번호, 국번, 뒷번호 따로따로 조회하려는 경우
- 단일번호 하나에서 찾는 거라서 search() 함수로 가능
tel = '010-1111-2345'
p = re.compile(r"(\d{2,3})-(\d{3,4})-(\d{4})")
# (패턴1)-(패턴2)-(패턴3)
# => 0 : 전체 index 1 : 패턴1 2 : 패턴2 3: 패턴3
m = p.search(tel)
print("전화번호:",m.group()) # 전체 패턴과 일치한 문자열
print("전화번호:",m.group(0)) # 전체 패턴과 일치한 문자열
print("통신사/지역번호:", m.group(1))
print("국번:", m.group(2))
print("뒷번호:", m.group(3))
3.1. 여러 전화번호에서
- 여러 전화번호들 속에서 뽑아야 하기에 -> findall
# 전체를 튜플로 각 하위패턴을 원소로 가지는 튜플로 반환
tel = '010-1111-2345 02-2433-1232 016-231-2323'
p = re.compile(r"(\d{2,3})-(\d{3,4})-(\d{4})")
result = p.findall(tel)
print(result)
>> [('010', '1111', '2345'), ('02', '2433', '1232'), ('016', '231', '2323')]
=> findall 을 쓰면 각각 하위 뽑기가 어렵다. 그래서 아래 처럼.
=> 아래처럼 사용하자.
3.2. 여러 전화번호에서
- finditer 반복문으로 여러 전화번호를 뽑았다.
tel = '010-1111-2345 02-2433-1232 016-231-2323'
p = re.compile(r"(\d{2,3})-(\d{3,4})-(\d{4})")
result_iter = p.finditer(tel)
for m in result_iter:
print("전체번호", m.group())
print("지역/통신사", m.group(1))
print("국번:", m.group(2))
print("뒷번호:", m.group(3))
print("-"*30)
>>>
전체번호 010-1111-2345
지역/통신사 010
국번: 1111
뒷번호: 2345
------------------------------
전체번호 02-2433-1232
지역/통신사 02
국번: 2433
뒷번호: 1232
------------------------------
전체번호 016-231-2323
지역/통신사 016
국번: 231
뒷번호: 2323
------------------------------
4. 중첩그룹이라면?
- 중첩 그룹. 그룹안에 그룹. 그렇다면 이때 group 번호는 어떤 순서?
왼쪽 --> 오른쪽
바깥쪽 패턴 --> 안쪽 패턴
num = '0000-1111-2222-3333'
p = re.compile(r"(\d{3,4})-((\d{4})-(\d{4}))-(\d{4})")
m = p.search(num)
print("전체:",m.group())
print("group(1):",m.group(1))
print("group(2):",m.group(2))
print("group(3):",m.group(3))
print("group(4):",m.group(4))
print("group(5):",m.group(5))
>>>
전체: 0000-1111-2222-3333
group(1): 0000
group(2): 1111-2222
group(3): 1111
group(4): 2222
group(5): 3333
# 0 전체
1 첫 괄호
2 두번째 괄호(밖 전체)
3 두번째 괄호 안 첫번째 괄호
4 두번째 괄호 안 두번째 괄호
5 세번째 괄호
4.1.2 패턴 내에서 하위그룹 "참조"
- `\번호`
- 지정한 '번호' 번째 패턴으로 매칭된 문자열과 같은 문자열을 의미
- 지정했다는 것은 하위패턴으로 괄호를 묶어줬다는 것
0. findall()과 비교.
tels ="""
010-1111-2222
010-3333-3333
010-3232-9090
02-4321-4321
011-5353-1010
"""
#1 일반 전화번호 패턴 - findall(tels):5개 모두 찾는다.
p1 = r"(\d{2,3})-(\d{3,4})-(\d{4})"
print(re.findall(p1,tels))
>>> [('010', '1111', '2222'), ('010', '3333', '3333'), ('010', '3232', '9090'), ('02', '4321', '4321'), ('011', '5353', '1010')]
1. 패턴 내에서 하위그룹 참조
- \2 의 의미 : 2번 하위그룹 패턴으로 찾은 값과 동일한 값(문자열) 이 이자리에 있어야 한다.
- 2번 하위그룹의 패턴 형식을 들고 오는 게 아님!
- 하위 그룹은 () 묶여 있는 것들만. \번호 -> 호출개념. 하위그룹으로 묶은게 아님.
# m.group(3) 은 현재 없다. 오류남.
# 만약 쓰려면 (\2) 이렇게 묶어줘야 함. 근데 뭐 앞과 같으니 묶을 일이 없
# 국번과 번호가 같은 전화번호를 찾아라.
tels ="""
010-1111-2222
010-3333-3333
010-3232-9090
02-4321-4321
011-5353-1010
"""
p2 = r"(\d{2,3})-(\d{3,4})-\2"
# \2 : 2번 하위그룹 패턴으로 찾은 값과 동일한 값(문자열) 이 이자리에 있어야 한다.
# 2번 하위그룹의 패턴 형식을 들고 오는 게 아님!
for m in re.finditer(p2,tels): # 함수형으로 iter써서 하나씩
print("번호:",m.group(0), " 지역번호:",m.group(1)," 국번:", m.group(2))
# 하위 그룹은 () 묶어야 함. \번호 -> 호출개념. 하위그룹으로 묶은게 아님.
# m.group(3) 은 현재 없다. 오류남.
# 만약 쓰려면 (\2) 이렇게 묶어줘야 함. 근데 뭐 앞과 같으니 묶을 일이 없
>> 번호: 010-3333-3333 지역번호: 010 국번: 3333
>> 번호: 02-4321-4321 지역번호: 02 국번: 4321
<활용 EX > 패턴 내에서 특정부분만 변경
# 전체가 g<0> (묶여 있는 애들)가 g<1>
# \g<1> : 1번 하위 그룹으로 찾은 내용을 그 위치에 표현.
# sub 바꿀 문자열 : \g<1>###### 이렇게 바꾸는 것!!
# => jumin변수의 문자열에서 패턴(주민번호)을 '\g<1>######'으로 변경
(1) 함수로
info ='''김정수 kjs@gmail.com 801023-1010221
박영수 pys@gmail.com 700121-1120212
이민영 lmy@naver.com 820301-2020122
김순희 ksh@daum.net 781223-2012212
오주연 ojy@daum.net 900522-1023218
'''
p = r"(\d{6}-[1-4])(\d{6})" # 전체가 g<0> (묶여 있는 애들)가 g<1>
result = re.sub(p, '\g<1>######', info)
# \g<1> : 1번 하위 그룹으로 찾은 내용을 그 위치에 표현.
# sub 바꿀 문자열 : \g<1>######
# => jumin변수의 문자열에서 패턴(주민번호)을 '\g<1>######'으로 변경
print(result)
>>>
김정수 kjs@gmail.com 801023-1######
박영수 pys@gmail.com 700121-1######
이민영 lmy@naver.com 820301-2######
김순희 ksh@daum.net 781223-2######
오주연 ojy@daum.net 900522-1######
(2) 객체로
info ='''김정수 kjs@gmail.com 801023-1010221
박영수 pys@gmail.com 700121-1120212
이민영 lmy@naver.com 820301-2020122
김순희 ksh@daum.net 781223-2012212
오주연 ojy@daum.net 900522-1023218
'''
p = re.compile(r"(\d{6}-[1-4])\d{6}")
# p.search(info) # 찾는 건 전체
# p.findall(info) #findall은 소그룹만 튜플로 묶는다. 리턴할 때 빠진 것. # 출력하면 ()친 딱 주민번호만 출력
result = p.sub('\g<1>######',info)
print(result)
>>>
김정수 kjs@gmail.com 801023-1######
박영수 pys@gmail.com 700121-1######
이민영 lmy@naver.com 820301-2######
김순희 ksh@daum.net 781223-2######
오주연 ojy@daum.net 900522-1######
** 성빼고 이름 숨기기
p1 = r"([가-힣])[가-힣]{2,3}"
result1 = re.sub(p1,"\g<1>모씨", info)
print(result1)
p2 = re.compile(r"([가-힣])[가-힣]{2,3}")
result2= p2.sub("\g<1>모씨", info)
print(result2)
>>>
김모씨 kjs@gmail.com 801023-1010221
박모씨 pys@gmail.com 700121-1120212
이모씨 lmy@naver.com 820301-2020122
김모씨 ksh@daum.net 781223-2012212
오모씨 ojy@daum.net 900522-1023218
김모씨 kjs@gmail.com 801023-1010221
박모씨 pys@gmail.com 700121-1120212
이모씨 lmy@naver.com 820301-2020122
김모씨 ksh@daum.net 781223-2012212
오모씨 ojy@daum.net 900522-1023218
<Greedy(기본), Non-Greedy 탐색>
- greedy : 최대 일치
- 이게 기본
- 주어진 패턴을 만족하는 문자열을 최대한 넓게(길게) 잡아서 찾는다.
- non-greedy : 최소 일치
- 주어진 패턴을 만족하는 문자열을 최소한으로 잡아서 찾는다.
- 개수의 끝이 정해지지 않은 수량자(개수를 나타내는 메타문자) 뒤에 '?'를 붙인다.
- ex) `*?` `+?` `{m,n}?` `{m,}?`
1. greedy : 최대 일치
import re
# txt 안에서 html 태그만 조회 : <태그이름(문자열로 된)>
txt = "<h1>파이썬 정규표현식</h1><b>메타문자</b><br><div>test</div>안녕하세요" # html
p = re.compile(r"<.+>")
# > 을 만나도 .으로 모든 글자 중 하나로 보았기 때문이다.
# 가장 멀리 있는 거 > 기준으로 찾아 거기까지 찾은 것.
# => 최대로 찾았다. => greedy 방식
result = p.findall(txt)
print(result)
>> ['<h1>파이썬 정규표현식</h1><b>메타문자</b><br><div>test</div>']
2. non-greedy : 최소 일치
# non-greedy : 최소 일치
import re
# txt 안에서 html 태그만 조회 : <태그이름(문자열로 된)>
txt = "<h1>파이썬 정규표현식</h1><b>메타문자</b><br><div>test</div>안녕하세요" # html
p = re.compile(r"<.+?>")
# ? 붙였다. 최소한
result = p.findall(txt)
print(result)
>> ['<h1>', '</h1>', '<b>', '</b>', '<br>', '<div>', '</div>']
'AI_STUDY > Python' 카테고리의 다른 글
Python _DB : pymysql 을 이용해 mysql 연동 (0) | 2022.06.04 |
---|---|
아나콘다 가상환경 생성 및 활성화 (Python, Windows) & 패키지 pip 설치 (pandas jupyter matplotlib) (0) | 2022.06.03 |
python 프로그래밍 : 텍스트 파일 입출력 (0) | 2022.05.19 |
python 프로그래밍 : 예외와 예외처리 (0) | 2022.05.18 |
python 프로그래밍 : 자료 구조_리스트 (0) | 2022.05.16 |