ㅅㅇ

python 프로그래밍 : 정규표현식 본문

AI_STUDY/Python

python 프로그래밍 : 정규표현식

SO__OS 2022. 5. 23. 23:58

_플레이데이터 빅데이터캠프 공부 내용

 

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
        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>']