파이썬 Study/라이브러리

re (regex - regular expression)

코르시카 2021. 3. 13. 11:56

 

출처:

https://bradbury.tistory.com/47

 

Python 공부 - 정규식(Regular expression)

정규표현식(Regular expression) - 문자열 비교나 처리를 하기 위한 유용한 특수기호 표현식 - 정규식은 그 자체로 하나의 언어이며 축약된 형식 언어의 한 종류 정규식 종류 ^  라인의 처음을 매칭(문

bradbury.tistory.com

https://wikidocs.net/1642

 

위키독스

온라인 책을 제작 공유하는 플랫폼 서비스

wikidocs.net

https://whatisthenext.tistory.com/116

 

[파이썬] 정규표현식(regular expression)

정규표현식 정규표현식(Regular Expressions) re 모듈 : 파이썬 정규 표현식을 지원한다. 파이썬에서는 정규 표현식을 지원하기 위해 re(regular expression) 모듈을 제공한다. 자바(JAVA)에서 패턴 객체(p)의

whatisthenext.tistory.com


■ re는 사용이 어렵고... 정리 하여도 잘 까먹으므로 

- search 기능 / match 기능을 어느정도 잘 쓸 수 있게 하자!

- escapeㅁ 문자 \  이용하기 위해 , 반드시 text 열 앞에 r로 raw text로 만드는 것 중요

> ex :   r'c{1, }'

 

■ Search

1) 정의 :

문자열에서 특정 표현( ~= 패턴 ) 찾기

- f.y.i] str의 find( ) method도 존재 : 찾는 문자가 문자열 안에 없으면 -1, 있으면 위치 index 값 리턴


■ Search - 축약형

- 한줄로 코드 절약 가능

import re

hand = 'This is a message From: your friend!'
if re.search('From:',hand): # this is not None -> if specific character exsists
    print(re.search('From:',hand))

>>> <re.Match object; span=(18, 23), match='From:'>

print(f'hand[18:23] : {hand[18:23]}')
>>> hand[18:23] : From:
source = 'Lux, the Lady of Luminosity'
m = re.match('[a-z]+', source )

■ Search - 컴파일 후 매칭

- 객채  object 생성 후 여러번 재사용 가능

- 반복 패턴의 경우 미리 컴파일을 하여 빠른 사용 가능

import re 
 
# re 내장모듈 내(.) compile 메서드를 사용. 
# compile 메서드는 "패턴 객체"를 반환한다. 
p = re.compile('[a-z]+') 

# 패턴 객체(p)에는 또다시 검색 메서드가 있다.       
m = p.match("python")    

■ 메타문자 - 패턴생성

 . ^ $ + ? { } [ ] \ | ( )

패턴 표

-----------------------------------------------------------------

^

&

라인의 처음을 매칭(문장의 시작)

-----------------------------------------------------------------

$

라인의 끝을 매칭(문장의 끝)

-----------------------------------------------------------------

.

\n 제외 임의의 문자를 매칭 (와일드 카드)

점 하나는 글자 하나를 의미

-----------------------------------------------------------------

\s

공백 문자를 매칭(\n, \t, \b)

-----------------------------------------------------------------

\S

공백이 아닌 문자를 매칭

-----------------------------------------------------------------

\d

숫자(1digit) === [0-9]

-----------------------------------------------------------------

\D

특수문자(!,%,$...) + 텍스트 + 화이트스페이스

-----------------------------------------------------------------

*

바로 앞선 문자에 적용되고 0 혹은 그 이상의 앞선 문자와 매칭을 표기함

(앞의 문자가 여러번 반복 될 수 있다)

-----------------------------------------------------------------

{m, n}

m회 이상 n회 이하

-----------------------------------------------------------------

*?

바로 앞선 문자에 적용되고 0 혹은 그 이상의 앞선 문자와 매칭을

탐욕적이지 않은 방식으로 표기

-----------------------------------------------------------------

+

바로 앞선 문자에 적용되고 1 혹은 그 이상의 앞선 문자와 매칭을 표기함

(앞의 문자가 1번 이상 나타난다)

-----------------------------------------------------------------

?

0회 이상 1회 이하

-----------------------------------------------------------------

+?

바로 앞선 문자에 적용되고 1 혹은 그 이상의 앞선 문자와 매칭을

탐욕적이지 않은 방식으로 표기함

-----------------------------------------------------------------

\

이스케이프, 또는 메타 문자를 일반 문자로 인식하게 하는 시작 기호

-----------------------------------------------------------------

[ ]

문자 클래스

-----------------------------------------------------------------

[aeiou]

명시된 집합 문자에 존재하는 단일 문자와 매칭.

ex) 위는 “a”, “e”, “i”, “o”, “u” 문자만 매칭되는 예제

-----------------------------------------------------------------

[a-z0-9]

- 기호로 문자 범위를 명세할 수 있다.

소문자이거나 숫자인 단일 문자만 매칭되는 예제

-----------------------------------------------------------------

( )

괄호가 정규표현식에 추가될 때, 매칭을 무시한다.

하지만 findall()을 사용 할 때 전체 문자열보다 매칭된 문자열의

상세한 부속 문자열을 추출할 수 있게 한다.

→ 그룹핑, 추출할 패턴을 지정한다

-----------------------------------------------------------------

|

or 조건식 줄 때 사용


■ 컴파일 후 사용법


1) [ ] 대괄호 사용법

- 문자 "한 개" 와 매칭된다

(a) 대괄호 안에 아무 문자클래스를 넣을 수 있음

    엄밀하게 구분이 된다

    ( ex : a / A, z / Z ) 가 다름 : 소문자 ↔ 대문자  는 서로 같은 문자가 아니다 (다른 문자)

(b) 안에 들어간 것들 끼리 - 하이픈으로 연결 가능

     ( ex : [a-A] == \S, [0-9] == \d ) : 축약 표현은 문자클래스 지정 안함(바로 쓴다)

(c) ^ 개행은 문자 클래스 [ ] 에서는 not  ....  BUT   ....  문자열에서는 처음과 일치를 의미

(d) 사이에 메타 문자 끼워넣기 :  [.], [-] 등은 실제 문자 자체를 의미 . 과 -, 즉 메타문자 → 실제 문자

a[a-zA-Z0-9]z  →  aBz, a9z 등과 매칭

[0-9]                       \d    숫자

[^0-9]                     \D    텍스트 + 특수문자 + 화이트스페이스

[a-zA-Z0-9]          \w    텍스트 + 숫자( 언더스코어 _ 포함 )

[^a-zA-Z0-9]       \W   특수문자 + 공백

[ \t\n\r\f\v]         \s     whitespace 문자

[^ \t\n\r\f\v]       \S     whitespace 아닌 것 = 텍스트 + 특수문자 + 숫자


2) Dot( . ) 사용법

- 문자 "한 개" 와 매칭된다(그 어떤 문자이던 \d + \D == \w + \s)

(a) 도트 두개는 문자 두개와 매칭된다

(b) 도트 문자 포함의 범위는 숫자, 문자, 특수문자 모드를 포함

a.z  →  axz, a9z, a!z 등과 매칭


3) 반복 ( * ) 사용법

- 바로 앞 문자 / 문자 클래스 가 "0 번" 이상 반복

(a) 문자가 없을 수도 있을 때 사용

.*  →  하나 이상의 문자를 포함하는 문자열

ab*c  →  ac, a123c, abbbbclike.*  →  like, likely, .... like로 시작하는 그 어떤 문자열과 매치


4) 반복 ( + ) 사용법

- 바로 앞 문자 / 문자 클래스 가 "1 번" 이상 반복

ca+t  →  cat, caaat, caaaat

[A-Z]+  →  대문자로만 이루어진 문자열 ABC, DEF ...

 

import re

source = "Luke Skywarker 02-123-4567 luke@daum.net"  # \w와 \w+의 차이 
 
m1 = re.findall('\w', source) # 단어가 아니라 문자 단위로 출력 
m2 = re.findall('\w+', source) # 단어 단위로 출력, greedy하게 최대 출력
print("m1 : ", m1)
print("m2 : ", m2)
 
>>> 출력결과
m1 :  ['L', 'u', 'k', 'e', 'S', 'k', 'y', 'w', 'a', 'r', 'k', 'e', 'r', '0', '2', '1', '2', 
       '3', '4', '5', '6', '7', 'l', 'u', 'k', 'e', 'd', 'a', 'u', 'm', 'n', 'e', 't']
m2 :  ['Luke', 'Skywarker', '02', '123', '4567', 'luke', 'daum', 'net']

5) 반복 { } 사용법

- 바로 앞 문자 / 문자 클래스 반복횟수 지정

ca{2}t a가 2회 반복되어야 함 caat
ca{2,5}t a가 2회 이상 5회 이하 반복되어야 함 caat, caaat, caaaat
ca{0, }t 반복횟수 0회 이상 (*와 동일) ct, cat, caat, caaat, caaaat
cat{0, 1}t 반복횟수 0회 ~ 1회 이하 (?와 동일) ct, cat
cat{ , 3} 반복횟수 0회 이상 ~ 3회 이하 ct, cat, caat, caat

 


6) 반복 ? 사용법

- 바로 앞 문자 0회 / 1회 둘 중 하나 등장과 매치

ab?c b가 있어도 되고 없어도 된다. ac, abc

7) string 안의 반복문 찾기

-  r'(.)\1{1, }'

  1. r 을 써주어야 \를 글자로 인식 (raw text화)
  2. ( )으로 . 과 매치되는 single 문자열 찾고 그룹을 부여
  3. \1 로 ( ) 첫 그룹에서 찾은 문자를 아직 매칭을 찾지 않은 남은 문자열에서 한번 더 찾음(이 시점에서 2개 반복되는 char)
  4. { } 로 바로 앞 글자 1회 이상 반복으로 2회 이상 반복 char를 찾음
import re

stringList = ['112342221234441', 'aabbbcdaeegh']

# group 서치 했기 때문에 find iter로 group 별로 뽑아야 함
reObj = re.compile(r'(.)\1{1,}') 

for string in stringList:
    reIter = reObj.finditer(string)
    for obj in reIter:
        print(obj)
        print(obj.group())
    
    print('\n')
        
    reSearch = reObj.search(string)
    print(reSearch.group())
    
    print('\n')
    
    for findrtn in reObj.findall(string):
        print(findrtn)
    
    print('-='*3)
    print('\n'*2)

>>> # re의 iter 매써드만 잘 나옴
<re.Match object; span=(0, 2), match='11'>
11
<re.Match object; span=(5, 8), match='222'>
222
<re.Match object; span=(11, 14), match='444'>
444


<re.Match object; span=(0, 2), match='aa'>
aa
<re.Match object; span=(2, 5), match='bbb'>
bbb
<re.Match object; span=(8, 10), match='ee'>
ee
    

8) [부정/긍정] [전방/후방] 탐색

(a) 긍정 전방탐색 (?=<regex>)

(b) 긍정 후방탐색 (?<=<regex>)

(c) 부정 전방탐색 (?!<regex>)

(d) 부정 후방탐색 (?<!<regex>)

[   =   ]  : 긍정 /  [ ! ]  : 부정
[ 공백 ]  : 전방 /  [ < ] : 후방

▶ ( ) 로 감싸서 group 화 시킴

▶ <p> 태그 안의 모든 텍스트를 검사

<p> 태그는 포함하지만 결과에는 제외하고 싶음, <p>는 정규식의 일치부에 넣지 않도록 함

▶ 후방탐색과 전방탐색을 문자 덩어리 앞뒤에 쓰면

    <p> 태그 안이라는 조건을 설정하면서 동시에 <p> 태그는 일치부에서 제외시킬 수 있음

> 예시를 통한 이해

print(re.search('(?<=<p>)\w+(?=</p>)', 
                'Kakao <p>ryan</p> keep a straight face.').group())

>>> ryan

→ 후방까지 <p>를 찾고 전방에는 </p>로 끝나는 패턴에 그리디한 \w + 로 태그 안을 검색,

    감싸는 <p></p>는 제외

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

>>> http
# foo.bar, autoexec.bat, sendmail.cf 같은 형식의 파일
import re
reObj = re.compile(".*[.](?!bat$|exe$).*$")
#                 all_letter + dot + (no ending with bat | exe) +(조건:ends in one line)

print(reObj.findall("foo.bar"))
>>> ['foo.bar']

print(reObj.findall("autoexec.bat"))
>>> []

print(reObj.findall("autoexec.bat_"))
>>> ['autoexec.bat_']

print(reObj.findall("sendmail.cf"))
>>> ['sendmail.cf']

9) Greedy VS Non-Greedy

(a) Greedy

- 해당 문자 : *, + , { }

- 문자는 greedy, 최대한 매칭하게 찾기 때문에 최대일치 패턴을 찾음

import re
source = <li>나이키</li><li>아디다스</li><li>퓨마</li>
m = re.match('<li>.*</li>', source)
if m:
    print(m10.group())
    
>>> <li>나이키</li><li>아디다스</li><li>퓨마</li>

(b) Non-greedy

- (a) 의 greedy 문자 바로 우측에 ? 문자를 붙여주면 최소일치 패턴으로 바뀜

# non-greedy
print(re.match('<.*?>', s).group())
>>> <html>

 

10) ★위의 메타 문자들의 특성

(a) 소비형 메타 문자

>  +, *, ?, { } 는 문자열 소모(소비)하는 방식, 반복 조건을 마치면 검색에서 제외

   (=== 문자열의 위치가 변하면서 검색)

 

(b) 비소비형 메타 문자

>  |, ^, $, \, ( ) 는 문자열의 위치와 관련 없는 절대 속성을 패턴으로 삼는다

- | : or

p = re.compile('a|b') # a 또는 b를 찾는다. 
m = p.findall("abcdefg")
print(m)
 
>>> ['a', 'b']

 

- ^, & : 맨 처음 일치

m1 = re.findall("^Life", "Life is too short")
m2 = re.findall("^is", "Life is too short")

print("m1 결과 : ", m1)
>>> m1 결과 :  ['Life']

print("m2 결과 : ", m2)
>>> m2 결과 :  []

 

- $ : 맨 마지막 일치

m1 = re.findall("short$", "Life is too short")
m2 = re.findall("short$", "Life is too short. So what?")
 
print("m1 결과 : ", m1)
>>> m18 결과 :  ['short'] # 문장 끝이 short기 때문에 short를 반환한다. 

print("m2 결과 : ", m2)
>>> m19 결과 :  [] # 문장 끝이 short가 아니기 때문에 반환값이 없다. 

11) ( ) Grouping

- 검색 결과의 특정 부분만을 출력

- ( )  감싼 부분으로 검색 index 이 되는것이라고 이해

 

> group method 가능한 Match매써드

   match, search : re.Match 객체를 돌려주므로 group 매써드 사용 가능한 애들

   <re.Match>.group( ) : re.Match 안의 패턴 검색된 것 모두 return 됨

import re
p = re.compile("(\w+)\s+(\d+[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")

print(m)
>>> <re.Match object; span=(0, 18), match='park 010-1234-1234'>

print(f'type(m):{type(m)}')
>>> type(m):<class 're.Match'>

print(f'm.group:{m.group}')
>>> m.group:<built-in method group of re.Match object at 0x7efc891eadb0>

print(f'm.group() : {m.group()}')
>>> m.group() : park 010-1234-1234

print(m.group(0))
>>> park 010-1234-1234

print(m.group(1))
>>> park

print(m.group(2))
>>> 010-1234-1234

12) re.Match 객체의 매써드

- match / search 매써드로 수행 된 결과값인 re.Match 객체에 대한 내용

(a) group( ) : 매치 된 문자열을 돌려준다

(b) start( ) : 매치 된 문자열의 시작 위치를 돌려준다

(c) end( ) : 매치 된 문자열의 끝 위치를 돌려준다

(d) span( ) : 매치된 문자열의  (시작, 끝) 의 튜플을 돌려준다


13) 알면 좋은 compile option

>  VERBOSE, X

- 정규식을 주석 / 줄단위로 구분하여 compile 하기 위한 옵션

(a) 원본

import re
reg_1 = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')
# VERBOSE / X form

reg_1 =  re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE) # re.X

 VERBOSE / X form

- 훨씬 가독성이 좋음

- 주석을 적고 여러 줄로 표현하는 것이 가능, 줄 단위로 #기호를 사용하여 주석문을 작성

- re.VERBOSE 옵션을 사용하면 문자열에 사용된 whitespace는 컴파일할 때 제거

  (단 [ ] 안에 사용한 whitespace는 제외)


■ 실제 compile 된 패턴의 검색법

 


1-1) match : 문자열의 처음부터 compile 된 정규식과 매치되는지 조사(fail 시 None return)

                    → 반드시 '첫' 케이스가 중요할 때 사용

1-2) search : 문자열 전체를 compile 된 정규식과 매치되는지 조사

> 발견 되면 그 지점에서 search / match를 멈춘다

존재시re.Match  객체가 return 된다

# match와 search 
import re

source13 = '''
All That Is Gold Does Not Glitter
'''
match = re.match("Not", source13)
search = re.search("Not", source13)
 
# re.Match 객체가 없는 경우 None
if match: 
    print("match : ", match.group()) # group 매써드로 모두 출력
>>> None
 
if search:
    print("search : ", search.group()) # group 매써드로 모두 출력
>>> search :  Not

2-1) findall : 정규식과 매치되는 모든 문자열을 list로 반환

2-2) finditer : 정규식과 매치되는 모든 문자열을 iterator로 반환 - for문으로 next( ) 매써드 사용하여 값을 출력하거나 형변환

findall 을 주로 쓰는게 편함

import re
p = re.compile('[a-z]+') # 소문자(a-z)가 1회 이상 반복되는 걸 찾아와라. 
m = p.findall("Life is to short")

print(type(m))
>>> <class 'list'>

print(m)
>>> ['ife', 'is', 'to', 'short']

3) split :  compile 된 정규식 일치하는 부분을 나누기

obj = re.compile( <pattern to search> )
obj.split(<string to find pattern>, [N] ) # N : number of times to perform split

import re

reObj = re.compile('<[^<>]*>')
print(reObj.split(
     '<html> Wow <head> header </head> <body> Hey </body> </html>', 
     2))
>>> ['', ' Wow ', ' header </head> <body> Hey </body> </html>']

 

4) sub : compile 된 정규식 일치하는 부분 대체

obj = re.compile( <pattern to search> )
obj.sub( <string to replace with>, <string to find pattern>, [count = N] ) # count : number of times to replace 

import re

p = re.compile('(blue|white|red)')
p.sub('colour', 'blue socks and red shoes')
>>> 'colour socks and colour shoes'

p.sub('colour', 'blue socks and red shoes', count=1)
>>> 'colour socks and red shoes'

 

 

 

 

 

 

반응형