5.1. 정규 표현식 살펴보기

- 주민등록번호를 포함하고 있는 텍스트에서 주민등록번호의 뒷자리 부분만 * 문자로 변경하기: re 라이브러리 사용

* re 없이

data = """
park 800905-1049118
kim  700905-1059119
"""

result = []
for line in data.split("\n"):
	word_result = []
	for word in line.split(" "):
    	if len(word) == 14 and word[:6].isdigit() and word[6:7] == "-" and word[7:].isdigit():
        	word = word[:7] + "*******"
        word_result.append(word)
    result.append(" ".join(word_result))
print("\n".join(result))

 

* re 사용하면

import re

data = """
park 800905-1049118
kim  700905-1059119
"""

pattern = re.compile("(\d{6})[-]\d{7}")
print(pattern.sub("\g<1>-*******", data))

 

5.2. 정규 표현식 시작하기

- 메타 문자

[ ] [^] 
. ? * + { }
^ $ \
\A \Z \b \B
( ) |

 

표기 설명 예시
[ ] 문자 클래스로 [ ] 사이의 문자중 하나와 매칭 [a-zA-Z] : 알파벳 중 하나와 매칭
[0-9] : 숫자 중 하나와 매칭
[^] not. ^ 뒤의 문자가 아닌 문자와 매칭 [^0-9] : 숫자가 아닌 문자 중 하나와 매칭
\^ : '^' 문자와 매칭
. \n 을 제외한 모든 문자 중 하나와 매칭 a.b : 'a0b' 와 매칭
a[.]b : 'a.b' 와 매칭
? ? 앞의 문자가 있거나 없거나 가능 ab?c : 'ab', 'abc' 와 매칭
* * 앞의 문자가 0~무한대로 반복 가능 ca*t : 'ct', 'cat', 'caat' 와 매칭
+ + 앞의 문자가 1~무한대로 반복 가능 ca+t : 'cat', 'caat' 와 매칭
{m}, {m,} {,m} {m, n} { 앞의 문자가 m ~ n 번 반복 가능  ca{2}t : 'caat' 와 매칭
ca{2,5}t : 'caat', 'caaat', 'caaaaat' 와 매칭
^
$
^ 다음 문자로 시작하는 문자를 매칭
$ 앞의 문자로 끝나는 문자를 매칭
^a...s$ : 'abyss'
\
r
정규식 엔진 규칙의 '\' 문자에 대한 escape 
파이썬 리터럴 '\' 에 대한 escape
r'\\section' : '\section' 와 매칭
* r은 Raw String 으로 '\\\\section' 으로 쓰게 하는 파이썬 리터럴 규칙(얘를 무시하게 되어)과 정규식 엔진 규칙의 '\' 중복 적용으로 인한 복잡성을 줄인다.
\A \Z
\b \B
re.MULTILINE 옵션 사용시에도 전체 문자열의 처음(\A), 끝(\Z) 하고만 매칭
단어 구분자(Word boundary) 와 매칭(\b), 매칭 안됨(\B)
r'\bclass\b' : ' class ' 와 매칭
r'\Bclass\B' : 'declassified'의 'class'와 매칭
| or. A|B 라는 정규식이면 A 또는 B 'Crow|Servo' : 'Crow', 'Servo' 와 매칭
( ) ( | )
\1
문자열의 반복과 같은 정규식 작성시 그룹을 만들어줌
첫번째 그루핑된 문자열 재참조
'(ABC)+' : 'ABCABCABC'

\g<1> \g<phone> sub 메소드 사용 시 그룹핑된 문자열 재참조  
*?, +?, ??, {m,n}? Non-Greedy. 가능한 가장 최소의 문자열 매칭 '<.*?>' : '<html>'

 

- 자주 사용하는 문자 클래스

\d, \D : [0-9], [^0-9]

\s, \S : [ \t\n\r\f\v], [^ \t\n\r\f\v]

\w, \W: [a-zA-Z0-9_], [^a-zA-Z0-9_]

* ^ 보다는 - 얘가 우선 순위를 갖는 것 같다.

 

- re 모듈 사용하기

import re
p = re.compile('[a-z]+') # re.compile('[정규표현식]', re.[옵션]) 으로 옵션을 줄 수도 있다.
# p 는 패턴으로 정규식을 컴파일한 결과이다.

### - 패턴 객체의 대표적 검색 메소드
m = p.match("python") # 문자열 시작부분에서 정규식과 매치되는 부분을 찾는다.
m = p.search(data) # 문자열 전체중에 정규식과 매치되는 부분을 찾는다.
list = p.findall(data) # 정규식과 매치되는 모든 문자열을 리스트로 돌려준다.
miter = p.finditer(data) # 정규식과 매치되는 모든 문자열을 match 요소를 갖는 반복 가능한 객체로 돌려준다.
# match, search는 정규식과 매치될 때는 match 객체를, 매치되지 않았을 때는 None 을 돌려준다.

### - match 객체의 메소드
m.group() # 매치된 문자열을 돌려준다. 'python'
m.start() # 매치된 문자열의 시작 idx를 돌려준다. 0
m.end() # 매치된 문자열의 끝 idx를 돌려준다. 6
m.span() # 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다. (0, 6)

* re.compile, p.match -> re.match 로 축약 가능하다.

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

 

- 패턴 컴파일 옵션: re.DOTALL, re.IGNORECASE, re.MULTILINE, re.VERBOSE

import re
p = re.compile('a.b', re.DOTALL) # a\nb 도 매치시킨다.
p = re.compile('[a-z]', re.IGNORECASE) # 대소문자 구분 없이 매치시킨다.

p = re.compile('^python\s\w+', re.MULTILINE) # 아래 data 에 대해 findall 시 ^ 를 각 줄마다 적용한다. 
data = ""python one
python two"""

charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);') # re.VERBOSE 옵션을 이용해서 아래와 같이 표현할 수 있다.
charref = re.compile(r"""
&[#]					# 숫자 엔터티의 참조 시작인 '&#' 문자 매칭 검사
(
	0[0-7]+				# 8진수 형태인 '036720' 과 같은 문자 매칭 검사
    | [0-9]+			# 또는 10진수 형태인 '80' 과 같은 문자 매칭 검사
    | x[0-9a-fA-F]+		# 또는 16진수 형태인 'xa82' 과 같은 문자 매칭 검사
)
;						# Trailing 세미콜론인 ';' 과 같은 문자 매칭 검사
""", re.VERBOSE)
charref.findall("&#012;&#983;&#x7845;")

 

5.3. 강력한 정규 표현식

- 그루핑된 부분의 문자열 뽑아내기: m.group(1)

# '이름 + " " + 전화번호' 찾기
p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group(0)) # 매치된 전제 문자열, park 010-1234-1234
print(m.group(1)) # 첫번째 그룹에 해당되는 문자열, park
print(m.group(2)) # 두번째 그룹에 해당되는 문자열, 010-1234-1234
print(m.group(3)) # 그룹이 중첩되는 경우 바깥쪽부터 안쪽 순으로 인덱스가 증가한다. 010

 

- 그루핑된 문자열 재참조하기: \1

import re
p = re.compile(r'(\b\w+)\s+\1')
print(p.search('Paris in the the spring').group()) # 'the the'
# * '\b\w+\s+' 에 매칭되는 건 'Paris ', 'in ', 'the ', 'the ', 'spring ' 이고, '(\b\w+)\s+\1' 에 매칭되는 건 'the the' 이다.

 

- 그루핑된 문자열에 이름 붙이기: (?P<그룹명> ), 재참조시 (?P=그룹이름)

import re
p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group("name")) # 'park'

p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
m = p.search("Paris in the the spring")
print(m.group()) # 'the the'

 

- 전방 탐색(Lookahead Assertions): 정규식과 일치하는 문자열에서 특정 정규식을 제외하고 출력

* 긍정형 전방 탐색: (?=...) - ... 에 해당하는 정규식과 매치되어야 하며 조건에 맞아도 문자열이 소비되지 않는다.

* 부정형 전방 탐색: (?!...) - ... 에 해당하는 정규식과 매치되지 않아야 하며 조건에 맞아도 문자열이 소비되지 않는다.

# 프로토콜 가져오기
import re
p = re.compile('.+(?=:)')
m = p.search("http://google.com")
print(m.group()) # 'http'

# 파일 이름을 특정 확장자 제외하고 가져오기
p = re.compile('.+[.](?!bat$|exe$).+') # bat, exe 제외하기
m = p.search("build.bat") # None
m = p.search("memo.txt") # <re.Match object; span=(0, 8), match='memo.txt'>

 

- 문자열 바꾸기: sub

import re
p = re.compile('(blue|white|red)')
print(p.sub("colour", "blue socks and red shoes")) # 'colour socks and colour shoes'
print(p.sub("colour", "blue socks and red shoes", count=1))  # 'colour socks and red shoes'
print(p.subn("colour", "blue socks and red shoes")) # ('colour socks and colour shoes', 2)

# sub 메소드 사용 시 그루핑 문자 참조
p = re.compile(r'(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)')
print(p.sub("\g<phone> \g<name>", "park 010-1234-1234")) # '010-1234-1234 park'
print(p.sub("\g<2> \g<1>", "park 010-1234-1234-1234")) # '010-1234-1234 park'

# sub 메소드의 매개변수로 함수 넣기 - 10진수를 16진수로 대체하기
def hexrepl(match):
	value = int(match.group())
    return hex(value)
    
p = re.compile(r'\d+')
print(p.sub(hexrepl, "Call 65490 for printing, 49152 for user code.")) # 'Call 0xffd2 for printing, 0xc000 for user code.'

 

- Non-Greedy: 만족하는 최소 문자 찾기

# Greedy
import re
s = "<html><head><title>Title</title>"
print(re.match('<.*>', s).group()) # '<html><head><title>Title</title>'

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

 

블로그 이미지

uchacha

개발자 일지

,