정규 표현식6
- 전방 탐색
- 예를 먼저 살펴보자
import re
p = re.compile(".+:")
m = p.search("http://google.com")
print(m.group())
- 정규식 .+:과 일치하는 문자열로 http:를 돌려주었다. 만약 http://라는 검색 결과에서 :을 제외하고 출력하려면 어떻게 해야할까? 위 예는 그나마 간단하지만 훨씬 복잡한 정규식이어서 그루핑은 추가로 할 수 없다는 조건까지 더해진다면 어떻게 해야 할까?
- 이럴 때 사용할 수 있는 것이 바로 전방 탐색이다. 전방 탐색에는 긍정과 부정의 2종류가 있고 다음과 같이 표현될 수 있다.
- 긍정형 전방 탐색((?=...)) - ...에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
- 부정형 전방 탐색((?!...)) - ...에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
- 먼저 긍정형 전방 탐색부터 살펴보자, 긍정형 전방 탐색을 사용하면 http:의 결과를 http로 바꿀 수 있다. 다음 예를 살펴보자
p = re.compile(".+(?=:)")
m = p.search("http://google.com")
print(m.group())
- 정규식 중 :에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경하였다. 이렇게 되면 기존 정규식과 검색에서는 동일한 효과를 발휘하지만 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아(검색에는 포함되지만 검색 결과에는 제외됨) 검색 결과에서는 :이 제거된 후 돌려주는 효과가 있다.
- 다음 정규식을 또 살펴보자
.*[.].*$- 이 정규식은 파일이름 + . + 확장자를 나타내는 정규식이다. 이 정규식은 예를 들어 foo.bar, autoexe.bat, sendmail.cf 같은 형식의 파일과 매치될 것이다.
- 이 정규식에 확장자가 bat인 파일은 제외해야 한다."는 조건을 추가해 보자. 가장 먼저 생각할 수 있는 정규식은 다음과 같다.
.*[.][^b].*$- 이 정규식은 확장자가 b라는 문자로 시작하면 안 된다는 의미이다. 하지만 이 정규식은 foo.bar라는 파일마저 걸러낸다. 정규식을 다음과 같이 수정해보자.
.*[.]([^b]..|.[^a].|..[^t])$- 이 정규식은 | 메타 문자를 사용하여 확장자의 첫 번째 문자가 b가 아니거나 두 번째 문자가 a가 아니거나 세번 째 문자가 t가 아닌 경우를 의미한다. 이 정규식에 의하여 foo.bar는 제외되지 않고 autoexec.bat은 제외되어 만족스러운 결과를 돌려준다. 하지만 이 정규식은 아쉽게도 sendmail.cf처럼 확장자의 문자 개수가 2개인 케이스를 포함하지 못하는 오동작을 하기 시작한다.
- 따라서 다음과 같이 바꿀 필요성이 있다.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$- 확장자의 문자 개수가 2개여도 통과되는 정규식이 만들어졌다. 하지만 정규식은 점점 더 복잡해지고 이해하기 어려워진다.
- 그런데 여기에서 bat파일 말고 exe 파일도 제외하라는 조건이 추가로 생긴다면 어떻게 될까? 이 모든 조건을 만족하는 정규식은 구현하려면 패턴은 더욱더 복잡해질 것이다.
- 부정형 전방 탐색
- 이러한 상황에서 우리의 돌파구는 부정형 전방 탐색이다. 위 예시는 부정형 전방 탐색을 사용하면 다음과 같이 간단하게 처리된다.
.*[.](?!bat$).*$- 확장자가 bat이 아닌 경우에만 통과된다는 의미이다. bat 문자열이 있는지 조사하는 과정에서 문자열이 소비되지 않으므로 bat가 아니라고 판단되면 그 이후 정규식 매치가 진행된다.
- exe 역시 제외하라는 조건이 추가되더라도 다음과 같이 간단히 표현할 수 있겠다.
.*[.](?!bat$|exe$).*$
- 문자열 바꾸기
- sub 메서드를 사용하면 정규식과 매치되는 부분을 다른 문자로 쉽게 바꿀 수 있다.
- 다음 예시를 살펴보자
p = re.compile('(blue|white|red)')
p.sub('colour','blue cocks and red shoes')
- sub 메서드의 첫 번째 매개변수는 바꿀 문자열(replacement)이 되고 두 번째 매개변수는 대상 문자열이 된다. 위 예에서 볼 수 있듯이 blue 또는 white 또는 red라는 문자열이 colour라는 문자열로 바뀌는 것을 확인할 수 있다.
- 그런데 딱 한 번만 바꾸고 싶은 경우도 있을 것이다. 이렇게 바꾸기 횟수를 제어하려면 다음과 같이 세 번째 매개 변수로 count값을 넘기면 된다,
p.sub('colour','blue socks and red shoes',count=1)
- 처음 일치하는 blue만 colour라는 문자열로 한 번만 바꾸기가 실행되는 것을 알 수 있다.
- sub 메서드와 유사한 subn 메서드
- subn 역시 sub와 동일한 기능을 하지만 반환 결과를 튜플로 돌려준다는 차이가 있다. 돌려준 튜플의 첫 번째 요소는 변경된 문자열이고, 두 번째 요소는 바꾸기가 발생한 횟수이다.
p = re.compile('(blue|white|red)')
p.subn('colour','blue socks and red shoes')
- 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"))
- 위 예는 이름+전화번호의 문자열은 전화번호+이름으로 바꾸는 예이다. sub의 바꿀 문자열 부분에 \g<그룹이름>을 사용하면 정규식의 그룹 이름을 참조할 수 있게 된다. </li>
- 다음과 같이 그룹 이름 대신 참조 번호를 사용해도 된다. </ul> </div> </div> </div>
- sub 메서드의 첫 번째 매개 변수로 함수를 넣을 수도 있다. 다음 예를 보자
- hex(x)는 정숫값을 입력 받아서 16진수 값인 Hexa-Decimal로 변환해서 돌려주는 함수이다.
- hexrepl 함수는 match 객체(위에서 숫자에 매치되는)를 입력으로 받아 16진수로 변환하여 돌려주는 함수이다. sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match객체가 입력된다. 그리고 매치되는 문자열은 함수의 반환 값으로 바뀌게 된다.
- Greedy $vs$ Non-Greedy
- 정규식에서 Greedy(탐욕스러운)란 어떤 의미일까, 다음 예제를 살펴보자
- <.> 정규식의 매치 결과로 문자열을 돌려주기를 기대했을 것이다. 하지만 </em>메타 문자는 매우 탐욕스러워서 매치할 수 있는 최대한의 문자열인
Title 을 모두 소비해 버렸다. 어떻게 하면 이 탐욕스러움을 제한하고 문자열까지만 소비하도록 막을 수 있을까?</li>- 다음과 같이 Non-greedy 문자인 ?을 사용하면 *의 탐욕을 제한할 수 있다.
</ul> </div> </div> </div>print(re.match('<.*?>',s).group())
</div>- Non-greedy 문자인 ?는 *?,+?,??,{m,n}?와 같이 사용할 수 있다. 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 한다.
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<2> \g<1>", "park 010-1234-1234"))
def hexrepl(match):
value = int(match.group())
return hex(value)
p = re.compile(r'\d+')
p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
s = '<html><head><title>Title</title>'
len(s)
print(re.match('<.*>', s).span())
print(re.match('<.*>', s).group())