파이썬 기초 6-2. 정규 표현식 심화

MANBOKWAK·2024년 1월 24일

파이썬 기초

목록 보기
15/15
post-thumbnail

정규 표현식 심화내용

1. 메타 문자

  • 앞선 포스팅에서 살펴보지 않은 메타 문자를 살펴보자

  • 앞에서 살펴본 메타문자 +,*,[],{} 등의 메타 문자는 매치가 진행 될 때 현재 매치되고 있는 문자열의 위치가 변경되는데 이를 보통 소비된다고 표현한다

  • +,*,[],{}와 다르게 문자열 소비가 없는 메타 문자에 대해 알아보자

①. |

  • | 메타 문자는 or과 동일한 의미로 사용됨
    ex) A|B라는 정규식은 A또는 B라는 의미
import re

pat = re.compile('Crow|Servo')
match = pat.match('CrowHello')
print(match)

출력 결과

  • Crow가 있으므로 match개체가 반환됨

②. ^

  • ^메타 문자는 문자열의 맨 처음과 일치함을 의미

  • compile옵션 MULTILINE과 자주 같이 사용됨

import re

print(re.search('^Life', 'Life is too short'))
print(re.search('^Life', 'My Life'))

출력 결과

  • Life로 시작하는 문자열은 match객체를 반환하고, Life로 시작하지 않는 문자열은 None를 반환하는것을 확인할 수 있음

③. $

  • $메타 문자는 ^와는 반대로 문자열의 끝과 매치함
import re

print(re.search('short$', 'Life is too short'))
print(re.search('short$', 'Life is too short, you need python'))

출력 결과

  • 문자열이 short로 끝나는 경우에는 match객체를 반환하지만, short로 끝나지 않는 경우에는 None를 반환하는것을 확인할 수 있음

④. \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'))

출력 결과

  • class왼쪽 또는 오른쪽에 공백이 하나라도 있으면 None을 반환하고, 양쪽에 공백이 없으면 match객체를 반환하는것을 확인할 수 있음

2. 그루핑

  • 특정한 문자열이 계속해섭 반복되는지 조사하는 정규식을 작성하고싶을때 필요한것이 그루핑

  • 그룹을 만들어주는 메타문자는 ()이다

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을 의미함


3. 그루핑된 문자열에 이름 붙이기

  • 정규식 안에 그룹이 무척 많을때 혼란스러울 수 있음

  • 정규식에 그룹 추가 및 삭제되면 그룹의 인덱스를 참조한 프로그램도 모두 변경해주어야함

  • 이러한 불편함을 방지하기위해 정규식은 그룹을 만들때 이름을 지정할 수 있음

  • (?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"))

출력 결과

  • name라는 그룹 이름을 참조한것을 확인할 수 있음

4. 전방탐색

  • 특정 문자 또는 문자열을 기준으로 앞쪽에 있고 패턴에 일치하는 문자열을 찾는 방법

  • 전방탐색은 ?=로 시작하고 등호 다음에 패턴 문자열이 오는 형식이다

  • 전방 탐색에는 두가지 종류가 있음

정규식종류설명
(?=...)긍정형 전방 탐색...에 해당하는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않음
(?!...)부정형 전방 탐색...에 해당하는 정규식과 매치되지 않아야하며 조건이 통과되어도 문자열이 소비되지 않음

①. 긍정형 전방 탐색

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$).*$


5.문자열 바꾸기

  • 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메서드를 사용할 때 참조 구문 사용하기

  • 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객체가 입력되고 매치되는 문자열은 함수의 반환값으로 바뀌게됨


6. Greedy vs Non-Greedy

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 문자열을 모두 소비해버림
  • '<.?>'처럼 ?를사용하면 ``의 탐욕을 제한할 수 있음
  • Non-Greedy 문자 ?는 *?, +?, ??, {m,n}?과 같이 사용할 수 있음
profile
Backend/ DevOps를 지망하는 곽희상입니다.

0개의 댓글