• ABC 문자열이 계속해서 반복되는지 조사하는 정규식을 작성하고 싶다고 해보자. 어떻게 해야할까. 지금까지 공부한 내용으로는 위 정규식을 작성할 수 없다. 이럴 때 필요한 것이 바로 그루핑(Grouping)이다.
  • 위 경우는 아래와 같이 그루핑을 사용하여 작성할 수 있다.
  • (ABC)+
    
    • 그룹을 만들어주는 메타 문자는 바로 ()이다.
import re
p = re.compile('(ABC)+')
m = p.search('ABCABCABC OK?')
print(m)
print(m.group())
<re.Match object; span=(0, 9), match='ABCABCABC'>
ABCABCABC
  • 다음 예를 보자
p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")
m = p.search("park 010-1234-1234")
  • \w+\s+\d+[-]\d+[-]\d+은 이름+" "+전화번호 형태의 문자열을 찾는 ㅡ정규식이다.
  • 그런데 이렇게 매치된 문자열 중에서 이름만 뽑아내고 싶다면 어떻게 해야할까?
  • 보통 반복되는 문자열을 찾을 때 그룹을 사용하는데, 그룹을 사용하는 보다 큰 이유는 위에서 볼 수 있듯이 매치된 문자열 중에서 특정 부분의 문자열만 뽑아내기 위해서인 경우가 더 많다.
  • 위 예에서 만약 이름 부분만 뽑아내려 한다면 다음과 같이 할 수 있다.
p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
m = p.search("park 010-1234-1234")
print(m.group(1))
park
  • 이름에 해당하는 \w+ 부분을 그룹 (\w+)으로 만들면 match 객체의 group(인덱스) 메서드를 사용하여 그루핑된 부분의 문자열만 뽑아낼 수 있다. group 메서드의 인덱스는 다음과 같은 의미를 갖는다.
  • group(0) : 매치된 전체 문자열
  • group(1) : 첫 번째 그룹에 해당되는 문자열
  • group(2) : 두 번째 그룹에 해당되는 문자열
  • group(3) : n 번째 그룹에 해당되는 문자열
p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group(2))
010-1234-1234
  • 이번에는 전화번호 부분을 추가로 그룹 (\d+[-]\d+[-]\d+)로 만들었다. 이렇게 하면 group(2)처럼 사용하여 전화번호만 뽑아낼 수 있다.
  • 만약 전화번호 중에서 국번만 뽑아내고 싶으면 어떻게 해야 할까? 다음과 같이 국번 부분을 또 그루핑하면 된다.
p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group(3))
010
  • 위 예에서 볼 수 있듯이 (\w+)\s+((\d+)[-]\d+[-]\d+)처럼 그룹을 중첩되게 사용하는 것도 가능하다. 그룹이 중첩되어 있는 경우는 바깥쪽부터 시작하여 안쪽으로 들어갈수록 인덱스가 증가한다.

  • 그룹의 또 하나 좋은 점은 한 번 그루핑한 문자열을 재참조할 수 있다는 점이다.
p = re.compile(r'(\b\w+)\s+\1')
p.search('Paris in the the spring').group()
'the the'
  • 정규식 (\b\w+)\s+\1은 (그룹) + " " + 그룹과 동일한 단어와 매치됨을 의미한다. 이렇게 정규식을 만들게 되면 2개의 동일한 단어를 연속적으로 사용해야만 매치된다. 이것을 가능하게 해주는 것이 바로 재참조 메타 문자인 \1이다. \1은 정규식의 그룹 중 첫 번째 그룹을 가리킨다.
  • 두 번째 그룹을 참조하려면 \2를 사용하면 된다.

  • 정규식 안에 그룹이 무척 많아진다고 가정해보자. 예를 들어 정규식 안에 그룹이 10개 이상만 되어도 매우 혼란스러울 것이다. 거기에 더해 정규식이 수정되면서 그룹이 추가, 삭제되면 그 그룹을 인덱스로 참조한 프로그램도 모두 변경해 주어야 하는 위험도 갖게 된다.
  • 만약 그룹을 인덱스가 아닌 이름(Named Groups)으로 참조할 수 있다면 어떨까? 그렇다면 이런 문제에서 해방될 수 있을 것이다.
  • 이러한 이유로 정규식은 그룹을 만들 때 그룹 이름을 지정할 수 있게 했다. 그 방법은 다음과 같다.
(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)
  • 위 정규식은 앞에서 본 이름과 전화번호를 추출하는 정규식이다.
  • 기존과 달라진 부분은 다음과 같다.
(\w+) --> (?P<name>\w+)
  • (\w+)라는 그룹에 name이라는 이름을 붙인 것에 불과하다. 여기에서 사용한 (?...) 표현식은 정규 표현식의 확장 구문이다. 이 확장 구문을 사용하기 시작하면 가독성이 상당히 떨어지긴 하지만 반면에 강력함을 갖게 된다.
  • 그룹에 이름을 지어 주려면 다음과 같은 확장 구문을 사용해야 한다.
(?P<그룹명>...)
p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
print(m.group("name"))
park
  • 위 예에서 볼 수 있듯이 name이라는 그룹 이름으로 참조할 수 있다.
  • 그룹 이름을 사용하면 정규식 안에서 재참조하는 것도 가능하다.
p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
p.search('Paris in the the spring').group()
'the the'
  • 위 예에서 볼 수 있듯이 재참조할 때에는
    (?P=그룹이름)
    
    이라는 확장 구문을 사용해야 한다.