[Do it! 점프 투 파이썬-스터디 노트] 07장 정규 표현식(07-3 강력한 정규 표현식의 세계로)

2021. 4. 23. 13:04PYTHON/Do it! 점프 투 파이썬

 

 

07장 정규 표현식

 

 

07-3 강력한 정규 표현식의 세계로

 

메타 문자

앞에서 살펴본 +,*,[],{} 등의 메타문자는 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 변경된다(소비된다). 이와 달리 문자열을 소비하지 않는 메타문자도 있다. 문자열 소비가 없는 메타문자(zero-width assertions)이다.

 

 

|

| 메타문자는 or과 동일한 의미로 사용된다. A|B라는 정규식이 있다면 A또는B라는 의미가 된다.

p=re.compile('Crow|Servo')
m=p.match('CrowHello')
print(m)
<re.Match object; span=(0,4), match='Crow'>

 

^

^ 메타문자는 문자열의 맨 처음과 일치함을 의미한다. 앞에서 살펴본 컴파일 옵션 re.MULTILINE을 사용할 경우에는 여러 줄의 문자열일 때 각 줄의 처음과 일치하게 된다. '^Life' 정규식은 Life 문자열이 처음에 온 경우에는 매치하지만 처음 위치가 아닌 경우에는 매치되지 않는다.

print(re.search('^Life', 'Life is too short'))
<re.Match object; span=(0,4), match='Life'>
print(re.search('^Life', 'My Life'))
None

 

$

$ 메타문자는 ^ 메타문자와 반대이다. $는 문자열의 끝과 매치함을 의미한다. 'short$' 정규식은 검색할 문자열이 short로 끝난 경우에는 매치되지만 그 이외의 경우에는 매치되지 않는다.

print(re.search('short$', 'Life is too short'))
<re.Match object; span=(12,17), match='short'>
print(re.search('short$', 'Life is too short, you need python'))
None

 

\A

\A는 문자열의 처음과 매치됨을 의미한다. ^메타문자와 동일한 의미지만 re.MULTILINE 옵션을 사용할 경우에는 ^은 각 줄의 문자열의 처음과 매치되지만 \A는 줄과 상관없이 전체 문자열의 처음하고만 매치된다.

 

\Z

\Z는 문자열의 끝과 매치됨을 의미한다. re.MULTILINE 옵션을 사용할 경우 $메타문자와는 달리 전체 문자열의 끝과 매치된다.

 

\b

\b는 단어 구분자(word boundary)이다. 보통 단어는 whitespace에 의해 구분된다.

p=re.compile(r'\bclass\b')
print(p.search('no class at all'))
<re.Match object; span=(3,8), match='class'>

'\bclass\b' 정규식은 앞뒤가 whitespace로 구분된 class라는 단어와 매치됨을 의미한다. 따라서 no class at all 의 class라는 단어와 매치된다.

print(p.search('the declassified algorithm'))
None

the declassified algorithm 문자열 안에도 class 문자열이 포함되어 있지만 whitespace로 구분된 단어가 아니므로 매치되지 않는다.

print(p.search('one subclass is'))
None

subclass 문자열 역시 class 앞에 sub 문자열이 더해져 있으므로 매치되지 않는다.

\b는 파이썬 리터럴 규칙에 의하면 백스페이스(BackSpace)를 의미하므로 백스페이스가 아닌 단어 구분자임을 알려주기 위해 r'\bclass\b'처럼 Raw string임을 알려주는 기호 r을 반드시 붙여주어야 한다.

 

 

\B

\B 베타문자는 \b 베타문자와 반대이다. whitespace로 구분된 단어가 아닌 경우에만 매치된다.

p=re.compile(r'\Bclass\B')
print(p.search('no class at all'))
None
print(p.search('the declassified algorithm'))
<re.Match object; span=(6,11), match='class'>
print(p.search('one subclass is'))
None

class 단어의 앞뒤에 whitespace가 하나라도 있는 경우에는 매치가 안 된다.

 

 

 

 

그루핑(Grouping)

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

(ABC)+
p=re.compile('(ABC)+')
m=p.search('ABCABCABC OK?')
print(m)
<re.Match object; span=(0,9), match='ABCABCABC'>
print(m.group(0))
ABCABCABC
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(n): n 번째 그룹에 해당하는 문자열

 

p=re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
m=p.search("park 010-1234-1234")
print(m.group(2))
010-1234-1234
p=re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
m=p.search("park 010-1234-1234")
print(m.group(3))
010

그룹을 중첩되게 사용하는 것도 가능하다. 그룹이 중첩되어 있는 경우는 바깥쪽부터 시작하여 안쪽으로 들어갈수록 인덱스가 증가한다.

 

 

그루핑된 문자열 재참조하기(Backreferences)

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를 사용하면 된다.

 

 

 

 

그루핑된 문자열에 이름 붙이기(Named Groups)

확장구문: (?P<그룹 이름>...)

(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)

(?...)표현식은 정규 표현식의 확장 구문이다.

 

p=re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
m=p.search("park 010-1234-1234")
print(m.group("name"))
park

 

그룹이름을 사용하면 정규식 안에서 재참조하는 것도 가능하다. 재참조할 때에는 (?P=그룹이름)이라는 확장구문을 사용해야 한다.

p=re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
p.search('Paris in the the spring').group()
'the the'

 

 

 

 

전방 탐색(Lookahead Assertions)

p=re.compile(".+:")
m=p.search("http://google.com")
print(m.group())
http:

▷긍정형 전방탐색(?=...): ...에 해당하는 정규식과 매치되어야하며 조건이 통과되어도 문자열이 소비되지 않는다.

▷부정형 전방탐색(?!...): ...에 해당하는 정규식과 매치되지 않아야하며 조건이 통과되어도 문자열이 소비되지 않는다.

 

 

긍정형(Positive) 전방 탐색

p=re.compile(".+(?=:)")
m=p.search("http://google.com")
print(m.group())
http

정규식 중 :에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경하였다. 이렇게 되면 기본 정규식과 검색에서는 동일한 효과를 발휘하지만 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아(검색에는 포함되지만 검색 결과에는 제외됨) 검색 결과에서는 :이 제거된 후 돌려주는 효과가 있다.

 

부정형(Negative) 전방 탐색

.*[.](?!bat$).*$

확장자가 bat가 아닌 경우에만 통과된다는 의미이다. bat 문자열이 있는지 조사하는 과정에서 문자열이 소비되지 않으므로 bat가 아니라고 판단되면 이후 정규식 매치가 진행된다. exe 역시 제외하라는 조건이 추가될 수도 있다.

.*[.](?!bat$|exe$).*$

 

 

 

 

문자열 바꾸기

sub메서드를 사용하면 정규식과 매치되는 부분을 다른 문자로 바꿀 수 있다.

p=re.compile('(blue|white|red)')
p.sub('colour'. 'blue socks and red shoes')
'colour socks and colour shoes'

sub 메서드의 첫 번째 매개변수는 바꿀 문자열(replacement)이 되고 두 번째 매개변수는 대상 문자열이 된다.

바꾸기 횟수를 제어하려면 세 번째 매개변수로 count 값을 넘기면 된다.

p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'

 

subn 역시 sub와 동일한 기능을 하지만 반환 결과를 튜플로 돌려준다는 차이가 있다. 돌려준 튜플의 첫 번째 요소는 변경된 문자열이고 두 번째 요소는 바꾸기가 발생한 횟수이다.

p=re.compile('(blue|white|red)')
p.subn('colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)

 

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"))
010-1234-1234 park

sub의 바꿀 문자열 부분에 '\g<그룹이름>'을 사용하면 정규식의 그룹 이름을 참조할 수 있다. 그룹이름 대신 참조번호를 사용해도 된다.

 

sub 메서드의 매개변수로 함수 넣기

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.')
'Call 0xffd2 for printing, 0xc000 for user code.'

hexrepl 함수는 match 객체(위에서 숫자에 매치되는)를 입력으로 받아 16진수로 변환하여 돌려주는 함수이다. sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력된다. 매치되는 문자열은 함수의 반환 값으로 바뀌게 된다.

 

 

 

 

 

Greedy vs Non-Greedy

s='<html><hea><title>Title</title>'
len(s)
32
print(re.match('<.*>', s).span())
(0,32)
print(re.match('<.*>', s).group())
<html><head><title>Title</title>
print(re.match('<.*?>', s).group())
<html>

non-greedy 문자인 ?는 *?,+?,??,{m,n}?와 같이 사용할 수 있다. 가능한 한 최소한의 반복을 수행하도록 한다.