TOP
class="layout-aside-left paging-number">
본문 바로가기
데이터분석 만능열쇠 [파이썬]/<파이썬 기초>

[파이썬] 파이썬기초: 정규 표현식(Regular Expressions) - 4

by 기록자_Recordian 2024. 6. 22.
728x90
반응형
시작에 앞서
해당 내용은 '<Do it! 점프 투 파이썬> 박응용 지음. 이지스 퍼블리싱' 을 토대로 작성되었습니다. 보다 자세한 내용은 해당 서적에 상세히 나와있으니 서적을 참고해 주시기 바랍니다.

이전 내용
 

[파이썬] 파이썬기초: 정규 표현식(Regular Expressions) - 3

시작에 앞서해당 내용은 ' 박응용 지음. 이지스 퍼블리싱' 을 토대로 작성되었습니다. 보다 자세한 내용은 해당 서적에 상세히 나와있으니 서적을 참고해 주시기 바랍니다.이전 내용 [파이썬]

puppy-foot-it.tistory.com


정규 표현식 - 전방 탐색 (Lookahead Assertions)

 

전방탐색이란 일치 항목을 찾을 때 특정 패턴 앞에 오는 다른 패턴을 확인하기 위해 사용되는 고급 정규 표현식 기법이다.

전방탐색 확장구문을 사용하면 암호문처럼 알아보기 어렵게 바뀌나, 전방탐색이 꼭 필요하고 매우 유용한 경우가 있다.

 

# 전방 탐색
p = re.compile(".+:")
m = p.search("http://google.com")
print(m.group())

▶ 정규식 ".+:"와 일치하는 문자열로 http:를 돌려주었다.


긍정형 전방 탐색, 부정형 전방 탐색

 

전방탐색에는 긍정(Positive)과 부정(Negative) 두 종류가 있다.

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

 

◆ 긍정형 전방 탐색

긍정형 전방 탐색을 사용하여 http:의 결과를 http로 바꾸기

# 긍정형 전방 탐색
p = re.compile(".+(?=:)")
m = p.search("http://google.com")
print(m.group())

▶ 정규식 중 :에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경

이러한 경우 기존 정규식과 검색에서는 동일한 효과를 발휘하나 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아 (검색에는 포함되나, 검색결과에선 제외) 검색 결과에서는 :이 제거된 후 돌려주는 효과가 있다.

 

다음은 '파일 이름 + . + 확장자'를 나타내는 정규식 '.*[.].*$'이다.

# 긍정형 전방 탐색2
p = re.compile('.*[.].*$') #파일명+확장자
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf")
print(m)
print(n)
print(o)

 

이 정규식에 확장자가 'bat 인 파일은 제외해야 한다'는 조건을 추가해보면

# bat 확장자 제외
#.*[.][^b].$ #문자 클래스 []안의 ^ 메타 문자는 반대(not) 의미
p = re.compile('.*[.][^b].*$')
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf")
print(m)
print(n)
print(o)

▶ 정규식 '.*[.][^b].$'은 확장자가 b라는 문자로 시작하면 안 된다는 의미로써, bat 뿐 아니라 bar 확장자 파일마저 걸러낸다.

 

따라서, 이를 수정해야 할 필요가 있다.

# bat 확장자 제외1 : | 메타 문자 사용
#.*[.][^b]..|.[^a].|..[^t])$s
#첫 번째 문자가 b가 아니거나, 두 번째 문자가 a가 아니거나, 세 번째 문자가 t가 아닌 경우
p = re.compile('.*[.][^b]..|.[^a].|..[^t])$s')
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf") #확장자가 2개인 케이스를 포함하지 못함
print(m)
print(n)
print(o)

▶ 정규식 '.*[.][^b]..|.[^a].|..[^t])$s' 은 

첫 번째 문자가 b가 아니거나, 두 번째 문자가 a가 아니거나, 세 번째 문자가 t가 아닌 경우를 의미하는데,

이를 수행하면 "sendmail.cf" 처럼 확장자의 문자 개수가 2개인 케이스를 포함하지 못하는 오작동을 일으키게 된다.

 

그래서 이를 또 수정해야 한다.

# bat 확장자 제외2
# .*[.]([^b].?.?|/[^a]?.?|..?[^t]?)$
# 확장자의 문자 개수가 2개여도 통과
p = re.compile('.*[.]([^b].?.?|/[^a]?.?|..?[^t]?)$')
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf")
print(m)
print(n)
print(o)

이제 확장자의 문자 개수가 2개여도 통과되는 정규식이 만들어졌다.

 

그러나, 여기에서 bat 파일에, exe 파일도 제외하라는 조건이 추가 된다면 패턴이 매우 복잡해질 것이다.


◆ 부정형 전방 탐색

이러할 때 부정형 전방 탐색을 사용하면 간단하게 처리된다.

# 부정형 전방 탐색
# bat 확장자 제외
p = re.compile('.*[.](?!bat$).*$')
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf")
print(m)
print(n)
print(o)

 

exe 역시 제외하라는 조건이 추가 되어도 간단히 표현할 수 있다.

# 부정형 전방 탐색
# bat 확장자 제외 + exe 확장자 제외
p = re.compile('.*[.](?!bat$|exe$).*$')
m = p.search("foo.bar")
n = p.search("autoexec.bat")
o = p.search("sendmail.cf")
r = p.search("excel.exe")
print(m)
print(n)
print(o)
print(r)


문자열 바꾸기(feat. sub메서드)

 

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

sub('바꿀 문자열', '대상 문자열')
# 문자열 바꾸기(feat. sub메서드)
p = re.compile('(blue|white|red)')
p.sub('colour', 'blue socks and red shoes')

▶ blue 또는 white 또는 red 문자열이 colour 라는 문자열로 바뀌었다.

 

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

sub('바꿀 문자열', '대상 문자열', count='바꿀 횟수')
# 문자열 바꾸기 + 횟수(count)
p = re.compile('(blue|white|red)')
p.sub('colour', 'blue socks and red shoes', count=1)

▶ 바꾸는 횟수를 1번으로 지정하니, 처음 일치하는 blue만 colour로 바뀌었다.


◆ subn: sub 메서드와 유사하나, 반환 결과를 튜플로 돌려준다.

# subn: 튜플로 반환
p = re.compile('(blue|white|red)')
p.subn('colour', 'blue socks and red shoes')

▶ 변경된 문자열과 변경된 횟수를 튜플로 반환한다.

 


  sub 메서드를 사용할 때 참조 구문 사용하기

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

# sub 매서드, 참조구문
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<phone> \g<name>", "Song 010-5678-1234"))

'이름 + 전화번호'의 문자열을 '전화번호 + 이름'으로 바꿨다.

 

그룹 이름 대신 참조 번호를 사용해도 된다.

# sub 매서드, 참조 번호
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<2> \g<1>", "Song 010-5678-1234"))


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

# 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.")

▶ hexrepl 함수는 match 객체(위에서 숫자에 매치되는)를 입력으로 받아 16진수로 변환하여 돌려주는 함수이다.

sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력된다.

그리고 매치되는 문자열은 핫무의 반환 값으로 바뀌게 된다.


Greedy vs Non-Greedy (탐욕법)

 

정규식에서 Greedy란,

일치하는 것을 최대한 많이 찾으려고 하는 것을 말한다.

# 탐욕법(Greedy) vs Non-Greedy
s = '<html><head><title>Title</title>'
print(len(s))

print(re.match('<.*>', s).span())
print(re.match('<.*>', s).group())

#non-greedy
print(re.match('<.*?>', s).group())

▶ '<.*>' 정규식에서 * 메타문자는 매치할 수 있는 최대한의 문자열인 '<html><head><title>Title</title>' 문자열을 모두 소비해 버린다.

'<html>' 문자열만 소비하기 위해서는 non-greedy 문자인 ? 를 사용하면 된다.

 

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


전체 코드

07. Regular_Expressions.ipynb
0.03MB

728x90
반응형