메타문자, 그룹핑, 전방탐색(긍정형, 부정형), 정규식에서의 Greedy
문자열이 소모되면 그 부분은 검색 대상에서 제외된다
하지만 소모되지 않는 경우에는 다음에 또 다시 검색대상이 된다고 생각하면 쉬울 것이다.
+, *, [], {} 등의 메타문자는 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 변경된다(보통 소모된다고 표현한다)
p = re.compile('Crow|Servo')
m = p.match('CrowHello')
print(m)
# 결과 : <_sre.SRE_Match object; span=(0, 4), match='Crow'>
print(re.search('^Life', 'Life is too short'))
# 결과 : <_sre.SRE_Match object at 0x01FCF3D8>
print(re.search('^Life', 'My Life'))
# 결과 : None
print(re.search('short$', 'Life is too short'))
# 결과 : <_sre.SRE_Match object at 0x01F6F3D8>
print(re.search('short$', 'Life is too short, you need python'))
# 결과 : None
^ 또는 $ 문자를 메타 문자가 아닌 문자 그 자체로 매치하고 싶은 경우는
[^], [$]처럼 사용하거나 \^, \$ 로 사용하면 된다.
^ 메타문자와 동일한 의미이지만 re.MULTILINE 옵션을 사용할
경우에는 다르게 해석된다.
re.MULTILINE 옵션 사용시 ^은 라인별 문자열의 처음과 매치되지만 \A는 라인과 상관없이 전체 문자열의 처음하고만 매치된다.
p = re.compile(r'\bclass\b')
print(p.search('no class at all'))
# 결과 : <_sre.SRE_Match object at 0x01F6F3D8>
'\bclass\b' 정규식은 앞 뒤가 whitespace로 구분된 class라는 단어와 매치됨을 의미한다.
print(p.search('the declassified algorithm'))
# 결과 : None
\b는 파이썬 리터럴 규칙에 의하면 백스페이스를 의미하므로, 백스페이스가 아닌 단어 구분자임을 알려주기 위해 Raw string임을 알려주는 기호 r을 반드시 붙여줘야한다. (r'\bclass\b')
p = re.compile(r'\Bclass\B')
print(p.search('no class at all'))
# 결과 : None
print(p.search('the declassified algorithm'))
# 결과 : <_sre.SRE_Match object at 0x01F6FA30>
print(p.search('one subclass is'))
# 결과 : None
ABC라는 문자열이 계속 반복되는지 조사하는 정규식을 작성하려면 그룹핑을 해야한다.
(ABC)+
그룹을 만들어 주는 메타 문자는 바로 (과 )이다.
p = re.compile('(ABC)+') m = p.search('ABCABCABC OK?') print(m) # 결과 : <_sre.SRE_Match object at 0x01F7B320> print(m.group(0)) # 결과 : ABCABCABC
(317p~~)
import re
p = re.compile(r"(\w+)\s+\d+[-](\d+)[-](\d+)")
m = p.search("park 010-1234-5678")
print(m.group(3))
위와 같이 ()로 그룹핑을 하는데
(\w) 만 그룹핑하면 park의 k만 m.group(1)에 나온다.
(\w+) 처럼 +까지 그룹핑하면 park 해당 정규식의 전체그룹이 다 나온다.
group(인덱스) 설명
group(0) 매치된 전체 문자열
group(1) 첫번째 그룹에 해당되는 문자열
group(2) 두번째 그룹에 해당되는 문자열
group(n) n번째 그룹에 해당되는 문자열
import re
p = re.compile(r"(\w+)\s+(\d+[-](\d+))[-](\d+)")
m = p.search("park 010-1234-5678")
print(m.group(2))
>>> 010-1234
print(m.group(3))
>>> 1234
p = re.compile(r'(\b\w+)\s+\1')
p.search("Paris in the the spring").group()
>>> 'the the'
정규식 '(\b\w+)\s+\1'은 '(그룹) + " " + 그룹과 동일한 단어'와 매치됨을 의미한다.
이렇게 정규식을 만들게 되면 2개의 동일한 단어가 연속적으로 사용되어야만 매치된다.
이것을 가능하게 해주는 것이 재참조 메타문자인 \1dlek.
\1은 정규식 그룹 중 첫번째 그룹을 지칭한다. 두번째 그룹을 참조하려면 \2를 사용하면 된다.
그룹을 인덱스가 아닌 이름(Named Groups)으로 참조가능
(?P\w+)\s+((\d+)[-]\d+[-]\d+)
(\w+) -->> (?P\w+)
import re
p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-5678")
print(m.group("name"))
>>> park
그룹명을 이용하면 정규식 내에서 재참조도 가능하다.
p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)') p.search("Paris in the the spring").group() >> 'the the'
재참조 할 때는 (?P=그룹명) 이라는 확장구문을 이용해야한다.
p = re.compile(".+:")
m = p.search("http://google.com")
print(m.group())
>>> http:
전방탐색에는 긍정(positive)과 부정(negative)의 2종류가 있다.
정규식 종류 설명
(?=...) 긍정형 전방탐색 ...에 해당하는 정규식과 매치되어야하며 조건이 통과되어도 문자열 소모X
(?!...) 부정형 전방탐색 ...에 해당하는 정규식과 매치되지 말아야하며
조건이 통과되어도 문자열 소모X
p = re.compile(".+(?=:)")
m = p.search("http://google.com")
print(m.group())
>>> http
긍정형 전방탐색이 적용되어 :에 해당되는 문자열이 정규식 엔진에 의해 소모되지 않아(검색에는 포함되지만 검색결과에는 제외됨) 검색결과에서는 :이 제거된 후 리턴된다.
.*[.].*$ # '파일명 + . + 확장자'를 나타내는 정규식
.*[.]([^b]..|.[^a].|..[^t])$ 처럼
bat를 명확하게 제거하자니 .cf 처럼 두자리 케이스를 포함하지 못한다.
.*[.][^b].*$ 처럼 첫글자가 b가 아닌걸로 하자니 bat 외에 bar라는 파일마저 걸러낸다.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
이제 확장자의 문자개수가 2개여도 통과되는 정규식이 되었지만 너무 복잡하다.
여기서 bat 말고 exe 파일도 제외하라는 조건이 추가로 생긴다면 더 복잡해질 것이다.
.*[.](?!bat$).*$ <<-- 이렇게 하면 위의 복잡한 식이 아니어도 간단히 해결된다.
.*[.](?!bat$|exe$).*$ <<-- exe 역시 제외하라는 조건이 추가되도 간단히 표현가능
sub메소드를 이용하면 정규식과 매치되는 부분을 다른 문자로 쉽게 바꿀 수 있다.
p = re.compile('(blue|white|red)') p.sub('colour', 'blue socks and red shoes') >> 'colour socks and colour shoes'
sub메소드의 첫번째 입력 인수는 '바꿀 문자열(replacement)'이 되고,
두번째 입력 인수는 '대상 문자열'이 된다.
만약 딱 한번만 바꾸고 싶은 경우 세번째 입력인수로 count값을 넘기면 된다.
p.sub('colour', 'blue socks and red shoes', count=1)
>>> 'colour socks and red shoes'
기능은 동일하지만 subn 메소드는 튜플로 리턴한다는 차이가 있다.
리턴된 튜플의 첫번째 요소는 변경된 문자열이고, 두번째 요소는 바꾸기가 발생한 횟수이다.p = re.compile('(blue|white|red)') p.subn('colour', 'blue socks and red shoes') >> ('colour socks and colour shoes', 2)
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<phone> \g<name>", "park 010-1234-5678"))
>>> 010-1234-5678 park
위 예는 '이름 + 전화번호'의 문자열을 '전화번호 + 이름' 으로 바꾸는 예이다.
sub의 바꿀 문자열 부분에 '\g<그룹명>'을 이용하면 정규식의 그룹명 참조가 가능하다.
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<2> \g<1>", "park 010-1234-5678"))
>>> 010-1234-5678 park
위와 같이 그룹명 대신 참조번호를 이용해도 마찬가지 결과가 리턴된다.
def hexrepl(match):
"Return the hex string for a decimal number"
value = int(match.group())
return hex(value)
p = re.compile(r'\d+')
p.sub(hexrepl, Call 65490 for printing, 49512 for user code.')
>>> Call 0xffd2 for printing, 0xc000 for user code.'
hexrepl 함수는 match 객체(위에서 숫자에 매치되는)를 입력으로 받아 16진수로 변환하여 리턴하는 함수이다.
sub의 첫번째 입력 인수로 함수를 사용할 경우 해당 함수의 첫번째 입력인수에는 정규식과 매치된 match 객체가 입력된다.
s = '<html><head><title>Title</title>'
len(s)
>>> 34
print(re.match('<.*>', s).span())
>>> (0, 32)
print(re.match('<.*>', s).group())
>>> <html><head><title>Title</title>
'<.*>' 정규식의 매치 결과로 <html>문자열이 리턴되기를 기대했지만
* 메타문자는 매우 탐욕스러워서 매치할 수 있는 최대 문자열인
<html><head><title>Title</title> 문자열을 모두 소모시켜 버렸다.
이 탐욕스러움을 제한하고 <html>이라는 문자열까지만 소모하려면? non-greedy 문자 사용
print(re.match('<.*?>', s).group())
>>> html
non-greedy 문자인 ?은 *?, +?, ??, {m,n}?과 같이 사용할 수 있다. 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 한다.