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("ϗ硅")
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>'
'Python' 카테고리의 다른 글
[Python] 자바와 비교하기 - 4. 클래스 외 (0) | 2021.07.26 |
---|---|
[Python] 자바와 비교하기 - 3. 함수, 입출력 (0) | 2021.07.23 |
[Python] 자바와 비교하기 - 2. 제어문 (0) | 2021.07.22 |
[Python] 자바와 비교하기 - 1. 자료형 (0) | 2021.07.22 |