시작전에 다시 정리하면
1.문자 클래스 [] : [] 사이의 문자들과 매치, 즉 [abc] 면 before 중 b에 매치
자주 쓰는 문자 클래스는
・ \d : 숫자와 매치, [0-9]와 동일한 표현식
・ \D : 숫자가 아닌 것과 매치, [^0-9]와 동일한 표현식
・ \s : whitespace 문자와 매치, [\t\n\r\f\v]와 동일현 표현 // \t 탭, \n 줄 바꿈, \r 줄 바꿈, 커서를 앞으로 이동,
// \f 줄 바꿈, 커서를 다음 줄로 이동, \v 수직 탭
・ \S : whitespace 문자가 아닌 것과 매치, [^\t\n\r\f\v]와 동일한 표현식
・ \w : 문자+숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일한 표현식
・ \W : 문자+숫자(alphanumeric)가 아닌 문자와 매치, [^a-zA-Z0-9_]
2. Dot(.)
\n을 제외한 모든 문자와 매치
a.b 는 "a + 모든문자 + b" // "aab", "a0b", "abc" 는 abc 빼고 다 매치됨, 근데 [.] 이건 그냥 도트임
3. 반복 (*)
ca*t 는 a가 0번이상 반복되면 된다. 즉 ct는 a가 0번 반복되니 매치, cat은 1번이니 매치, caaat는
3번 반복됐으니 매치
4. 반복 (+)
+는 1번 이상 반복된 경우 매치
5. 반복 ({m, n})
m~n회 사이 반복되면 매칭, n의 경우 2억 개 미만이면 된다. 그리고 m이나 n만 써도 된다.
6. ?
?의 경우 있어도 되고 없어도 된다. // ab?c의 경우, abc, ac 모두 매칭된다.
그리고 정규식을 이용한 문자열 검색 방법은 4가지다.
메소드 목적
match() 문자열의 처음부터 정규식과 매치되는지 조사 // 안맞으면 아무것도 반환 ㄴ
search() 문자열 전체를 검색하여 정규식과 매치되는지 조사
findall() 정규식과 매치되는 모든 문자열(substring)을 리스트로 반환
finditer() 정규식과 매치되는 모든 문자열을 반복 가능한 객체(iterator object)로 반환
반환된 결과를
메소드 목적
group() 매치된 문자열을 반환
start() 매치된 문자열의 시작 위치를 반환
end() 매치된 문자열의 끝 위치를 반환
span() 매치된 문자열의 (시작, 끝)에 해당항는 튜플 반환// (0, 6)의 형태
7. 컴파일 옵션
괄호는 얼리어스다.
・ DOTALL(S) : . 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 함
・ IGNORECASE(I) : 대소문자 관계없이 매치할 수 있도록 함
・ MULTILINE(M) : 여러줄과 매치할 수 있도록 함 (^, $ 메타문자의 사용과 관계있는 옵션)
・ VERBOSE(X) : verbose 모드를 사용할 수 있도록 함 (정규식을 보기 편하게 만들 수 있고, 주석 사용 가능)
p = re.compile('a.b', re.DOTALL) 의 형태로 패턴을 컴파일 할 때 사용
8. 백슬래시 문제
"\section" 이라는 문자열을 찾기위해 정규식을 만들면
\s가 whitespace로 해석되니 원하는 결과가 나오지 않는다.
의도대로 하려면 \\section 으로 해야할 거 같다.
근데 정규식을 만들어서 컴파일하면 실제 파이썬 정규식 엔진에는
파이썬 문자열 리터럴 규칙에 따라 \\이 \로 변경되어 \section이 전달된다. // 유닉스의 grep, vi
// 등에서는 문제 x
결국 위에있는 걸 찾으려면
p =. e.compile('\\\\section')으로 하든
이스케이프 문자를 무시하는 Raw String을 사용하여
p = re.compile(r'\\section')으로 해야한다.
9. 메타문자
・ | : or과 동일한 의미, A|B 라는 정규식이 있다면 A 또는 B라는 의미
・ ^ : 문자열 맨 처음과 일치함을 의미, 컴파일 옵션인 re.MULTILINE을 사용할 경우,
여러 줄의 문자열일 때 각 줄의 처음과 일치하게 됨
・ $ : ^와 반대, 문자열의 끝과 매치함을 의미
・ \A : 문자열의 처음과 매치됨을 의미, ^와 동일한 의미지만, re.MULTILINE 옵션과 사용할 경우,
다르게 해석된다. 옵션을 사용하면 ^은 각 줄의 문자열의 처음과 매치되지만, \A는 줄과 상관
없이 전체 문자열의 처음하고만 매치된다.
・ \Z : 문자열의 끝과 매치, \A와 동일하게 re.MULTILINE 옵션과 같이 쓰면 문자열 맨 마지막
・ \b : 단어 구분자(Word Boundary), 보통 단어는 whitespace에 의 해 구분된다.
p = re.compile(r'\bclass\b')
를 쓰면 class가 들어간 문자열을 매칭할 수 있다.
・ \B : \b와 반대, 안에 들어간 문자가 없어야 매칭이 된다.
10. 그루핑 (Grouping)
abc가 계속 반복되는지 조사하는 정규식을 만들 때 위에 있는 것만으로 못만든다.
그럴 때 필요한 게 그루핑이다.
(abc)+ 를 하면 abc가 반복되는지 확인 가능하다.
p = re.compile('(abc)+')
m = p.search('abcabcabc ok?')
print(m)
// <re.Match object; span(0, 9), match='abcabcabc'>
print(m.group())
// abcabcabc
import re
data = """
park 010-1234-5678
kim 010-1111-2222
im 010-3333-8888"""
p = re.compile(r'(\w+)\s+((\d+)[-]\d+[-]\d+)')
m = p.search("choi 010-1234-1234")
print(m.group(0))
print('========================')
print(p.sub("\g<2>", data))
위 코드를 보면 (\w+)에서 첫번째 괄호, ((\d+)[-]\d+[-]\d+) 두번째 중첩괄호가 있다.
중첩괄호의 경우, 바깥쪽부터 카운트해서, 괄호 전체가 \g<2>, (\d+)인 안쪽 괄호가 \g<3>이 된다.
즉 \g<1> 은 성, \g<2>은 전화번호 전체, \g<3>은 전화번호 국번이 나온다.
11. 그루핑 활용
그루핑을 하면 그 문자열을 재참조(Backreferences)할 수 있다.
import re
p = re.compile(r'(\b\w+)\s+\1')
print(p.search('Paris in the the spring').group())
// the the
정규식 (\b\w+)\s+\1 은 "(그룹) + " " + 그룹과 동일한 단어"와 매치됨을 의미한다. 이렇게 정규식을
만들게 되면 2개의 동일한 단어를 연속적으로 사용해야만 매치된다.
이것을 가능하게 해주는 것이 재참조 메타문자 \1이다
\1은 정규식의 그룹 중 첫번째 그룹, \2는 두번째 다.
그루핑된 문자열에 얼리어스 달기
프로그래밍은 혼자 하는 것이 아니다. 그룹이 많아지면 복잡하고 이해하는데 시간이 걸린다.
거기다, 정규식이 수정되면서 그룹이 추가, 삭제되면 그 그룹을 인덱스로 참조하는 프로그램도
변경해야 하고, 그러지 않으면 에러가 난다.
그룹을 인덱스가 아닌 이름(Named Group)으로 참조하면 된다.
방법은
(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)
이름과 전화번호를 추출하는 정규식이지만 (\w+) -> (?P<name>\w+) 여기서 추가된 (?...) 표현식은
정규 표현식의 확장 구문(syntex)이다.
이 확장 구문을 쓰면 가독성이 떨어지지만 참조하기 상당히 편해진다.
사용법은
(?P<그룹명>...)
사용법은
import re
p = re.compile(r'(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)')
m = p.search("park 010-1111-1111")
print(m.group('name'))
결과는 성이 나온다.
얼리어스로 재참조도 가능하다.
import re
p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
m = p.search("Paris in the the spring").group()
재참조할 때에는 (?P=그룹이름) 이라는 확장 구문을 사용해야 한다.
12. 전방 탐색
전방 탐색(Lookahead Assertions) 확장 구문을 알아보자면
import re
p = re.compile('.+:')
m = p.search("http://google.com")
print(m.group())
// http:
여기서 :을 제외하고 출력하는 방법은 전방 탐색이고, 긍정과 부정 2종류가 있다.
긍정형 전방 탐색(?=...) : ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도
문자열이 소비되지 않는다.
부정형 전방 탐색(?!...) : ... 에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도
문자열이 소비되지 않는다.
긍정형 전방 탐색
import re
p = re.compile('.+(?=:)')
m = p.search("http://google.com")
print(m.group())
// http
정규식 중 : 에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경하였다.
이렇게 되면 기존 정규식과 검색에서는 동일한 효과를 발휘하지만, :에 해당하는 문자열이 정규식 엔진
에 의해 소비되지 않아 (검색에는 포함되지만 검색 결과에는 제외됨) 검색 결과에서는 : 이 제거된 후
돌려주는 효과가 있다.
.*[.].*$
이 정규식은 "파일 이름 + . + 확장자" 를 나타내는 정규식이다. 이 정규식은 foo.bar, autoexec.bat,
sendmail.cf 같은 형식의 파일과 매치될 것이다.
만약 확장자가 bat인 파일은 제외한다 라는 조건을 추가하려면
.*[.][^b].*$
이 정규식은 확장자가 b로 시작하면 안된다는 의미다. 근데 이렇게 하면 foo.bar도 걸러낸다.
.*[.]([^b]..|.[^a].|..[^t])$
이 정규식은 | 메타 문자를 사용해서 확장자의 첫번째 문자가 b가 아니거나, 두번째 문자가 a가 아니거나
세번째 문자가 t가 아닌 경우를 의미한다.
그럼 foo.bar는 제외되지 않는다. 하지만 이 정규식은 .cf같이 확장자의 문자 개수가 2개인 케이스를
포함하지 못하는 오작동을 하기 시작한다.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
확장자 문자가 2개여도 통과되는 정규식이다. 하지만 정규식은 점점 더 복잡해지고, 이해하기 어려워진다.
부정형 전방 탐색
이럴 때 도움되는 게 부정형 전방 탐색이다.
.*[.](?!bat$).*$
확장자가 bat이 아닌 경우에만 통과된다는 의미이다. bat 문자열이 있는지 조사하는 과정에서 문자열이
소비되지 않으므로 bat이 아니라고 판단되면 그 이후 정규식 매치가 진행된다.
bat과 exe의 경우
.*[.](?!bat$|exe$).*$
13. 문자열 바꾸기
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
// sub 메소드와 유사한 subn 메소드
subn은 동일한 기능이지만 반환 결과를 튜플로 돌려주면서, 첫번째 요소는 변경된 문자열,
두번째 요소는 바꾸기가 발생한 횟수다.
sub 메소드 사용 시 참조 구문 사용하기
import re
p = re.compile(r'(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)')
print(p.sub("\g<phone> \g<name>", "park 010-1111-1111"))
//
010-1111-1111 park
위 예는 "이름 + 전화번호"의 문자열을 "전화번호 + 이름"으로 바꾸는 예다. sub의 바꿀 문자열 부분에
\g<그룹이름> 을 사용하면 정규식의 그룹 이름을 참조할 수 있게 된다.
그룹 대신 참조 번호 (\g<1> 등)를 사용해도 마찬가지 결과를 돌려준다.
sub 메소드의 매개변수로 함수 넣기
import re
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 객체가 입력된다. 그리고 매치되는 문자열은 함수의 반환 값으로 바뀌게 된다.
14. Greedy, Non-Greedy
Greedy(탐욕스러운)는
import re
s = '<html><head><title>Title</title>'
print(len(s))
print(re.match('<.*>', s).span())
print(re.match('<.*>', s).group())
//
32
(0, 32)
<html><head><title>Title</title>
<.*> 정규식의 매치 결과로 <html> 문자열을 돌려주기를 기대했지만, *메타 문자는 너무 탐욕스러워서
매치할 수 있는 최대한의 문자열인 <html><head><title>Title</title> 문자열을 모두 소비해 버렸다.
이 탐욕스러움을 제한하고, <html> 문자열까지만 소비하도록 막는 방법이 non-greedy문자인 ? 다
import re
s = '<html><head><title>Title</title>'
print(re.match('<.*?>', s).group())
//
<html>
non-greedy 문자인 ? 는 *?, +?, ??, {m, n}? 와 같이 사용할 수 있다.
가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 한다.
'파이썬' 카테고리의 다른 글
8주차, 맥에서 pandas (0) | 2022.02.07 |
---|---|
7주차, numpy 중간과정 (0) | 2022.02.01 |
6주차, 정규식 실습 (0) | 2022.01.31 |
6주차, 정규식 (0) | 2022.01.29 |
6주차, 파이썬 db (0) | 2022.01.29 |
댓글