정규 표현식 심화내용
앞선 포스팅에서 살펴보지 않은 메타 문자를 살펴보자
앞에서 살펴본 메타문자 +,*,[],{} 등의 메타 문자는 매치가 진행 될 때 현재 매치되고 있는 문자열의 위치가 변경되는데 이를 보통 소비된다고 표현한다
+,*,[],{}와 다르게 문자열 소비가 없는 메타 문자에 대해 알아보자
①. |
| 메타 문자는 or과 동일한 의미로 사용됨A|B라는 정규식은 A또는 B라는 의미import re
pat = re.compile('Crow|Servo')
match = pat.match('CrowHello')
print(match)
출력 결과

②. ^
^메타 문자는 문자열의 맨 처음과 일치함을 의미
compile옵션 MULTILINE과 자주 같이 사용됨
import re
print(re.search('^Life', 'Life is too short'))
print(re.search('^Life', 'My Life'))
출력 결과
③. $
$메타 문자는 ^와는 반대로 문자열의 끝과 매치함import re
print(re.search('short$', 'Life is too short'))
print(re.search('short$', 'Life is too short, you need python'))
출력 결과
④. \A
\A는 문자열의 처음과 매치됨을 의미함
^문자와 동일한 의미이지만, re.MULTILINE옵션을 사용할 경우에는 다르게 해석됨
re.MULTILINE곽 같이 사용할 경우 ^은 각줄의 문자열의 처음과 매치되지만, \A는 줄과 상관없이 전체 문자열의 처음하고만 매치됨
⑤. \Z
문자열의 끝과 매치됨을 의미
$과 동일한 의미이지만, re.MULTILINE과 같이 사용할 경우에는 다르게 해석됨
re.MULTILINE과 같이 사용할 경우 $은 각줄의 문자열의 끝과 매치되지만, \Z는 전체 문자열의 맨끝과 매치됨
⑥. \b
\b는 단어 구분자를 의미
보통 단어는 whitespace에 의해 구분됨
\b를 사용할때는 r'\bclass\b처럼 Raw String임을 알려주는 기호 r을 반드시 붙여주어야 사용할 수 있음
import re
p = re.compile(r'\bclass\b')
print(p.search('no class at all'))
출력 결과!
\bclass\b 정규식은 앞뒤가 whitespace로 구분된 class라는 단어와 매치됨을 의미
class라는 단어가 매치된것을 확인할 수 있음
subclass라는 단어는 매치가 되지 않은것을 확인할 수 있음
⑧.\B
\B 는 \b와 반대의 경우로 whitespace로 구분된 단어가 아닌 경우에만 매치됨import re
p = re.compile(r'\Bclass\B')
print(p.search('no class at all'))
print(p.search('the declassified algorithm'))
print(p.search('one subclass is'))
출력 결과

특정한 문자열이 계속해섭 반복되는지 조사하는 정규식을 작성하고싶을때 필요한것이 그루핑
그룹을 만들어주는 메타문자는 ()이다
import re
pat =re.compile('(ABC)+')
match = pat.search('ABCABCABC OK?')
print(match)
print(match.group(0))
출력 결과

(ABC)+를 이용해 그루핑을 한 예제import re
pat =re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
# \w+ : 문자열, \s+ : 공백, \d+ : 숫자, [-] : - 기호
match = pat.search("park 010-1234-1234")
print(match.group(1))
print(match.group(2))
print(match.group(3))
출력 결과

문자열에 해당하는 \w+부분을 그룹으로 만들면 match 객체의 group(index) 메서드를 사용하여 그루핑된 부분의 문자열만 추출할 수 있음
위 코드에서 확인할 수 있듯이 그룹을 중첩되게 사용하는것이 가능함 -> 그룹이 중첩되어 있는 경우 바깥쪽 부터 시작하여 안쪽으로 들어갈수록 인덱스가 증가
group(index) 메서드의 index는 아래와 같은 의미를 가짐
| group(index) | 설명 |
|---|---|
| group(0) | 매치된 전체 문자열 |
| group(1) | 첫 번째 그룹에 해당하는 문자열 |
| group(2) | 두 번째 그룹에 해당하는 문자열 |
| group(3) | n 번째 그룹에 해당하는 문자열 |
ⓐ 그루핑된 문자열 재참조하기
import re
pat = re.compile(r'(\b\w+)\s+\1')
print(pat.search('Paris in the the spring').group())
출력 결과

(\b\w+)\s+\1 이 정규식은 (그룹)+""+ 그룹과 동일한 단어와 매치됨을 의미
동일한 단어 2개가 연속으로 사용되어야만 매치됨 \1은 그룹 인덱스 1을 의미함
정규식 안에 그룹이 무척 많을때 혼란스러울 수 있음
정규식에 그룹 추가 및 삭제되면 그룹의 인덱스를 참조한 프로그램도 모두 변경해주어야함
이러한 불편함을 방지하기위해 정규식은 그룹을 만들때 이름을 지정할 수 있음
(?P<name>정규식) 형식을 통해서 문자열에 이름을 붙일 수 있음
#Example
(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)
import re
pat = re.compile(r"(?P<name>\w+)\s+((\d+)[-](\d+)[-](\d+))")
match = pat.search("park 010-1234-5678")
print(match.group("name"))
출력 결과

특정 문자 또는 문자열을 기준으로 앞쪽에 있고 패턴에 일치하는 문자열을 찾는 방법
전방탐색은 ?=로 시작하고 등호 다음에 패턴 문자열이 오는 형식이다
전방 탐색에는 두가지 종류가 있음
| 정규식 | 종류 | 설명 |
|---|---|---|
(?=...) | 긍정형 전방 탐색 | ...에 해당하는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않음 |
(?!...) | 부정형 전방 탐색 | ...에 해당하는 정규식과 매치되지 않아야하며 조건이 통과되어도 문자열이 소비되지 않음 |
①. 긍정형 전방 탐색
import re
pat = re.compile(".+(?=:)")
match = pat.search("https://www.google.com")
print(match.group())
출력 결과

전방탐색 (?=:)를 통해 https만 반환하도록 했음
기존 정규식과 검색에서는 동일한 효과를 발휘하지만 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않음 -> 검색에는 포함되지만 결과에는 포함x
# foo.bar, autoexec.bat, sendmail.cf같은 형식과 매칭됨
.*[.].*$
# .bat확장자는 제외해보자
.*[.][^b].*$
.*[.]([^b]..|.[^a].|..[^t])$
*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
.*[.].*$ 이 정규식은 '파일 이름 + . + 확장자'를 나타내는 정규식
.*[.][^b].*$ 정규식은 bat인 파일은 제외하기위해서 작성한 정규식 하지만 foo.bar라는 파일도 제외하므로 완벽하지 않음
.*[.]([^b]..|.[^a].|..[^t])$ 정규식은 메타 문자 |를 사용하여 확장자의 첫 번째 문자가 b가 아니거나 두 번째 문자가 a가 아니거나 세 번째 문자가 t가 아닌경우를 의미 -> foo.bar은 제외x autoexec.bat는 쉽게 제외, 하지만 이정규식은 sendmail.cf처럼 확장자의 문자수가 2개인 케이스를 포함x
*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$ 은 확장자의 문자수가 2개여도 통과되고 bat파일은 제외하는 정규식이다
② 부정형 전방 탐색
부정형 전방 탐색은 위와같은 복잡한 상황을 쉽게 해결할 수 있도록 도와줌
부정형 전방 탐색을 사용하면 위 예제를 간단한게 풀 수 있음 --> .*[.](?!bat$).*$
확장자가 bat문자열이 아닌경우에만 통과한다는 의미
여기에 더불어 exe도 제외하고싶다면 간단하게 |를 통해서 구현가능 --> .*[.](?!bat$|exe$).*$
sub메서드를 사용하면 정규식과 매치되는 부분을 다른 문자로 바꿀 수 있음
sub메서드의 첫번째 매개변수는 바꿀 문자열, 두번째 매개변수는 대상 문자열이됨
바꾸는 횟수를 count매개변수를 입력해줘서 지정할 수도 있음
import re
p = re.compile('(blue|white|red)')
print(p.sub('colour', 'blue socks and red shoes'))
print(p.sub('colour', 'blue socks and red shoes', count=1))
출력 결과
blue, red가 colour로 바뀌는것을 확인할 수 있음
coount = 1로주면 한번만 colour로 바뀌는것을 확인할 수 있음
① sub메서드를 사용할 때 참조 구문 사용하기
import re
p = re.compile(r'(?P<name>\w+)\s+(?P<phone>(\d+)[-](\d+)[-](\d+))')
print(p.sub('\g<name> \g<phone>', 'park 010-1234-1234'))
출력 결과
이름 + 전화번호 형식의 문자열을 전화번호 + 이름 형식으로 바꾸는 예시
sub메서드에서 바꿀 문자열부분에 /g<그룹 이름>을 사용하면 정규식의 그룹이름을 참조할 수 있음
②. sub 메서드의 매개변수로 함수 넣기
import re
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.'))
출력 결과
hexrepl 함수는 match객체를 입력으로 받아 16진수로 변환하여 돌려주는 함수
sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match객체가 입력되고 매치되는 문자열은 함수의 반환값으로 바뀌게됨
import re
# Greedy
s = "<html><head><title>Title</title></head></html>"
print(len(s))
print(re.match('<.*>', s).span())
print(re.match('<.*>', s).group())
#Non-Greedy
print(re.match('<.*?>', s).group())
출력 결과
<.*> 정규식의 매치 결과로 문자열을 돌려주기를 기대했지만, * 메타 문자는 탐욕스러워서 매치할 수 있는 최대한의 문자열인 Title 문자열을 모두 소비해버림?를사용하면 ``의 탐욕을 제한할 수 있음 ?는 *?, +?, ??, {m,n}?과 같이 사용할 수 있음