본문 바로가기
정규표현식

[ Python ] 정규표현식[3]

by fiasco 2022. 10. 27.
Mastering Python Regular Expressions

http://www.packtpub.com/


Authors : Félix López 

제3장  Grouping  : ( )

 

그룹화는 다음과 같은 작업을 수행할 수 있는 강력한 도구입니다.

  • quantifier를 적용하기 위한 subexpression생성
    예) 단일 문자가 아닌 subexpression을 반복합니다.
  • alternation 범위를 제한
    전체 표현식을 변경하는 대신 대체해야 할 항목을 정확히 정의할 수 있습니다.
  • 일치된 패턴에서 정보 추출
    예) 주문 목록에서 날짜를 추출합니다.
  • 추출된 정보를 정규식에서 재사용(가장 유용한 기능)
    예) 반복되는 단어를 감지하는 것입니다.

 

Introduction

subexpression 생성

괄호는 정규식 엔진에 괄호 안의 패턴이 하나의 단위처럼 취급되도록 한.

 

import re
print(re.match(r"(\d-\w){2,3}", r"1-a2-b")) # <re.Match object; span=(0, 6), match='1-a2-b'>

 

capturing - Group은 매치된 pattern을 캡쳐한다, 이 후 여러operation(하위 method, 정규표현식)에 이를 이용

 

import re
expr = r"(\d+)-\w+"
data = "1-a\n20-baer\n34-afcr"

pattern = re.compile(expr)
match = pattern.finditer(data)
result = match.__next__()      # 그룹 1로 이동  
print(result.group())          # 일치 항목 전체 반환 = group(0) => 1-a
print(result.group(1))         # 그룹 1의 1번째 요소 반환       => 1
result = match.__next__()      # 그룹 2로 이동
print(result.group(1))         # 20
result = match.__next__()
print(result.group(1))         # 34

Backreferences(재참조)

정규표현식 및 sub함수에서 매칭된 그룹을 \index형식으로 재사용 가능

 

import re
expr = r"(\w+) \1"              # \1 - 매칭된 첫째 그룹
data = r"hello hello world"

pattern = re.compile(expr)
match = pattern.search(data)
result = match.groups()
print(result)                # ('hello',)


expr = r"(\d+)-(\w+)"
data = "1-a\n20-baer\n34-afcr"
replace = r"\2-\1"

pattern = re.compile(expr)
s = pattern.sub(replace,data)
print(s)                # 'a-1\nbaer-20\nafcr-34'

Named groups(이름지워진 그룹)

syntax : (?P<name>pattern)

Named groups 이용

 

import re
expr = r"(?P<first>\w+) (?P<second>\w+)"
data = r"Hello world"

pattern = re.compile(expr)
match = pattern.search(data)
print(match.group("first"))   # Hello
print(match.group("second"))  # world

#---------------------------------------
import re
expr = r"(?P<word>\w+) (?P=word)"
data = r"hello hello world"

pattern = re.compile(expr)
s = pattern.search(data)
print(s.groups())                       # ('hello',)

#----------------------------------------
import re
expr = r"(?P<country>\d+)-(?P<id>\w+)"
data = "1-a\n20-baer\n34-afcr"
replace = r"\g<id>-\g<country>"         # \g<name>  - 이름에 의한 그룹

pattern = re.compile(expr)
s = pattern.sub(replace,data)
print(s)                                # a-1\nbaer-20\nafcr-34

Non-capturing groups 

무의미한 캡쳐된 그룹을 재사용, 참조되지 않게 설정

syntax : (?:pattern)

import re
expr = "Españ(?:a|ol)"
data = "Español"
match = re.search(expr,data )
print(match)           # <re.Match object; span=(0, 7), match='Español'>
print(match.groups())  # () : Non-capturing groups  / ('ol',) : capturing groups

 

Atomic groups : (?>pattern)

non-capturing group은 backtracking(되돌림, 재추적)를 비활성화하므로 패턴의 모든 가능성이나 경로를 시도하는 것이 의미가 없는 경우 회피할 수 있다.

※ regex 모듈 이용

import regex
expr = "(?>\w+)-\d"
data = "aaaaabbbbbaaaaccccccdddddaaa"
m = regex.match(expr,data)
print(m)                        # None

     [ "(\w+)-\d"   작업 순서 ]

     1.     regex engine은 처음 a와 매치한다.

     2.     regex engine은 대상의 모든 문자와 매치한다.

     3.     regex engine은 dash를 매치하지 못해 fail 상태이다.

     4.     regex engine은 backtracking하여 두번째 a를 가지고 1~3작업을 다시 수행한다.

     5.     4작업 반복

 

[ automic groups 적용된 작업 순서 ] : 3번 작업에 의해 4~5가 의미 없어 회피

Special cases with groups

Python은 정규 표현식을 수정하거나 일치 항목에 이전 그룹이 있는 경우에만 패턴을 일치시키는 데 도움이 되는 몇 가지 형태의 그룹을 제공합니다.

Flags per group  : (? letter)pattern

 

import re

a = re.findall(r"(?u)\w+", u"ñ")
print(a)                           # ['ñ']
b = re.findall(r"\w+", u"ñ", re.U)
print(b)                           # ['ñ']

yes-pattern|no-pattern

이전 패턴이 발견된 경우 패턴을 일치시키려고 합니다. 반면에 이전 그룹을 찾지 못한 경우 패턴 일치를 시도하지 않습니다.

(?(id/name)yes-pattern|no-pattern)

이 id의 그룹이 매치하면 그 지점에서  yes-pattern이 매치, 그렇지 않으면 no-pattern 매치한다.

import re
expr = r"(\d\d-)?(\w{3,4})-(?(1)(\d\d)|[a-z]{3,4})$"
data = "34-erte-22"
data2 = "erte-abcd"         # 그룹1 미일치하므로 그룹3무시
data3 = "34-erte"           # 그룹1 일치하나 그룹3 yes-pattern 실시
pattern = re.compile(expr)
m = pattern.match(data)
print(m)                    # <re.Match object; span=(0, 10), match='34-erte-22'>
m2 = pattern.match(data2)
print(m2)                   # <re.Match object; span=(0, 9), match='erte-abcd'>
m3 = pattern.match(data3)
print(m3)                   # None

Overlapping groups

import re
expr1 = r'(a|b)+'
expr2 = r'((?:a|b)+)'
expr3 = r'(a|b)'
data = 'abaca'

a1 = re.finditer(expr1,data)
for i in a1:
    print(i)
'''
<re.Match object; span=(0, 3), match='aba'>
<re.Match object; span=(4, 5), match='a'>
'''
a = re.findall(expr1, data)
print(a)                      # ['a', 'a']
b = re.findall(expr2, data)
print(b)                      # ['aba', 'a']
c = re.findall(expr3, data)
print(c)                      # ['a', 'b', 'a', 'a']

위의 그림에서 'abc'는 매치 상태이다. 그러나 캡쳐되는 그룹은 'a'이다(overlapping groups matching process)

이는 엔진이 모든 문자를 그룹화한다 하더라도 결과는 같다. 즉 마지막 'a'만 유지한다. 명심해야 할 것은 이것이 overlapping groups의 핵심이다.

'a', 'b'로 구성된 그룹을 capture하기 위하여 non-capturing groups를 이용하라!!!

'정규표현식' 카테고리의 다른 글

[ Python ] 정규표현식 Table 및 우선순위  (0) 2022.11.01
[ python ] 정규표현식[5]  (0) 2022.10.31
[ python ] 정규표현식[4]  (0) 2022.10.30
[ python ] 정규표현식[2]  (0) 2022.10.27
[ python ] 정규표현식[1]  (0) 2022.10.27