[Python] 정규 표현식 (Regular Expression)
특정한 조건에 부합하는 문자들을 parsing 할 때 사용하는 기법으로, 대부분의 언어가 문법이 동일하다.
1. 정규 표현식 문법
1-1. 문자 클래스
[] 사이의 문자들과 매치를 한다.Syntax | Description |
---|---|
[a-z] | a부터 z 문자까지 매치 만약 [a-c]의 경우 “before”과 매칭을 하면 b만 매칭됨 |
[0-9] | 0부터 9 문자들과 매치 |
백슬래시(\)를 통해
Syntax | Description |
---|---|
\d | 숫자와 매치, [0-9]와 동일한 표현 |
\D | 숫자가 아닌것과 매치, [^0-9]와 동일한 표현식 |
\s | 공백 문자, [ \t\n\r\f\v]와 동일한 표현식 |
\S | 공백문자가 아닌 것과 매치, [^ \t\n\r\f\v]와 동일한 표현식 |
\w | 문자+숫자와 매치, [a-zA-Z0-9_]와 동일한 표현식 |
\W | 문자+숫자가 아닌 문자와 매치, [^a-zA-Z0-9_]와 동일한 표현식 |
\\ | 백슬래시 단어를 쓰기 위해 \\ 가 필요한데, python에선 \\ 는 \ 로 치환되기 때문에 r"\\ ......" 와 같이 r 을 붙여서 raw string로 인지시켜준다. |
# \\ example p = re.compile(r"\\section") string = \ """ \section """ result = p.findall(string) print(result) # ['\\section']
1-2. Dot
줄바꿈(\n)을 제외한 모든 문자와 매치된다.
Syntax | Description |
---|---|
a.b | a와 b 문자 사이를 의미하는 모든 문자와 매치 예를 들면 “aab“, “a0b“의 경우 일치 “abc”는 a와 b 사이에 최소 하나의 문자가 없기 때문에 불일치 “abbbbbb”의 경우 앞에서부터 매치되기 때문에 “abb”가 매치 |
a..b | a와 b 사이 모든 문자 2개가 일치하는 경우를 비교 예를 들면 “abbbbbb”의 경우 “abbb”와 일치 |
1-3. 반복 (*)
앞에 특정 문자가 매칭되는 것을 확인한다.
Syntax | Description |
---|---|
ca*t | *의 앞 문자인 a가 0번 이상 반복 매칭되는 경우를 모두 찾음 예를 들면 “ct”는 “a”가 0번 반복되어 매치 되었기 때문에 일치한다. |
p = re.compile('ab*c') print(p.match("ac")) # Match print(p.match("abc")) # Match print(p.match("abdc")) # None
1-4. 반복 (+)
*와의 차이점은 0번을 인정 하느냐 하지 않느냐의 차이다.
Syntax | Description |
---|---|
ca+t | +의 앞 문자인 a가 1번 이상 반복 매칭되는 경우를 모두 찾음 예를 들면 “ct”는 “a”가 1번 반복되어 매치가 안되었기 때문에 불일치 |
[a-z]+ | a부터 z 문자가 1회 이상 나오는 경우를 찾음 |
p = re.compile('[a-z]+') print(p.match("")) # None print(p.match("aaa")) # Match
1-5. 반복 ({m,n}, ?)
Syntax | Description |
---|---|
a{2}b | {}의 앞 문자인 a가 2번 반복 매칭되는 경우를 모두 찾음 (1번, 3번의 경우는 찾지 않음) 예를 들면 “ab”는 “a”가 1번 반복되었기 때문에 불일치 |
a{2,5}b | a가 2 ~ 5번 반복되며 뒤에 문자가 b가 나오면 일치 예를 들면 “aaaaab”의 경우 일치 |
ab?c | ?는 {0,1}과 같은 표현으로, b가 0번 또는 1번 나올 경우 매치 |
p = re.compile('a{2}b') print(p.match("ab")) # None print(p.match("aab")) # Match print(p.match("aaabc")) # None p = re.compile('a{2,5}b') print(p.match("aaaaab")) # Match p = re.compile('ab?c') print(p.match("abc")) # Match print(p.match("ac")) # Match print(p.match("abbc")) # None
1-6. 메타문자
Syntax | Description |
---|---|
| | Or의 의미로 예를 들면 “apple|banana”는 apple 또는 banana에 대해서 찾음 |
^ | 문자열의 맨 처음을 나타냄 |
$ | 문자열의 맨 끝을 나타냄 |
\b | 공백을 나타내며, 역슬래시를 쓸 경우 r |
import re result = re.search("apple|banana", "apple is yammy") print(result) result = re.search("apple|banana", "this is banana") print(result)
1-7. Grouping ()
만약 ABC가 반복되는 것을 찾고 싶을 때는 grouping을 통해 다음과 같이 작성하면 된다.
import re p = re.compile("(ABC)+") string = "ABCABCABC Good" result = p.search(string) print(result)
<_sre.SRE_Match object; span=(0, 9), match='ABCABCABC'>
Grouping한 결과를 다시 reuse 할 수 있다.
import re p = re.compile(r"(\b\w+)\s+\1") # p = re.compile(r"(?P<g1>\b\w+)\s+(?P=g1)") string = "Korea is in the the winter" result = p.search(string) print(result)
<_sre.SRE_Match object; span=(12, 19), match='the the'>
위 코드는 앞에 공백과 단어, 그리고 space가 있고 동일한 단어 (\1)가 뒤에 동일하게 나타나는 경우를 찾는다. \1, \2는 group1, group2를 의미한다.
그리고 추가로 각 group에 대해 naming을 지정할 수 있다.
?P<Name>
import re p = re.compile(r"(?P<g1>\w+)\s+((\d+)[-]\d+[-]\d+)") string = "Shumin 010-1234-5678" result = p.search(string) print(result) result = p.search(string).group("g1") print(result)
<_sre.SRE_Match object; span=(0, 20), match='Shumin 010-1234-5678'> Shumin
1-8. 전방탐색
만약 검색할 조건과 출력하는 문자열이 다른 경우 다음과 같이 사용할 수 있다.
긍정형: (?=)
부정형: (?!)
import re #p = re.compile(".+:") p = re.compile(".+(?=:)") string = "http://google.com" result = p.search(string) print(result)
http
위 코드는 .+
을 통해 개행문자를 제외한 1개 이상 모든 문자에 대해서 :
가 나올 때 까지 찾는데 출력할 경우엔 제외할 수 있다.
import re p = re.compile(".*[.](?!txt$).*$", re.M) string = \ """ test.exe test.out test.txt """ result = p.findall(string) print(result)
['test.exe', 'test.out']
위 코드는 .
을 기준으로 좌우에 문자열을 찾아내는 프로그램으로 만약 txt
로 끝나는 문자열은 제외하는 프로그램이다.
1-9. Greedy vs. lazy
만약 반복되는 단어로 인해 내가 찾고자 하는 범위보다 넓은 범위에서 검색을 하는 경우도 있다. 이 때 ?
를 통해서 해결 가능하다.
import re p = re.compile("<.*>", re.M) # p = re.compile("<.*?>", re.M) string = "<html><head><title>title</title>" result = p.match(string) print(result)
<_sre.SRE_Match object; span=(0, 32), match='title'>
만약 첫 단어인 <html>
만 찾고자 할 경우 ?
를 추가해주면 .*?
로 하면 원하는 결과를 얻을 수 있다.
2. 사용법
기본적으로 python에선 re
module을 통해 정규표현식 기능을 제공한다.
2-1. compile
다음과 같이 정규표현식 규칙을 지정해준다.
import re p = re.compile("[a-z]+")
Options | Desciption |
---|---|
DOTALL (S) | 예를 들면 “a.b”의 pattern은 줄바꿈 문자를 제외한 모든 문자를 비교할 수 있지만, DOTALL option을 사용할 경우 줄바꿈 문자도 포함해서 찾을 수 있음 |
IGNORECASE (I) | 대소문자를 모두 포함해서 검색 |
MULTILINE (M) | 여러 줄로 문자열을 입력 할 때 각 라인별로 새롭게 적용할 때 사용 |
VERBOSE (X) | 정규표현식을 한 줄로 작성하게 되면 가독성이 떨어지기 때문에, 줄바꿈을 하더라도 정상적으로 동작할 수 있도록 하는 옵션 |
import re p = re.compile("a.b", re.DOTALL) string = "a\nb" result = p.match(string) print(result)
위 결과를 보면 개행문자가 있음에도 매치가 되는 것을 확인 할 수 있다.
import re p = re.compile("^python\s\w+") string = \ """python is language programming is hard python is better than C life is good """ result = p.match(string) print(result)
['python is']
위 결과를 보면 오직 처음 문자인 python is
만 매치가 된다, 즉 multiple line에 대해선 인지하지 못하였기 때문에, MULTILINE
option re.compile("^python\s\w+", re.M)
을 주면 결과가 달라진다.
['python is', 'python is']
#p = re.compile("^python\s\w+") pattern = \ """ ^python # Start word is python \s # Space \w # Word + # At least 1 word """ p = re.compile(pattern, re.VERBOSE|re.MULTILINE) string = \ """python is language programming is hard python is better than C life is good """ result = p.findall(string) print(result)
위와 같은 예시처럼 줄별로 comment를 쓰면서 조건을 작성할 수 있어서 유용한 옵션이다.
2-2. match
위 처럼 지정한 pattern을 바탕으로 찾고자 하는 문자열을 입력해준다. 참고로 문자열 가장 처음부터 바로 pattern matching을 진행하는게 search와의 차이점이다.
Return type은 match object로 다양한 정보들을 담고 있다.
Methods | Description |
---|---|
group() | 매치된 문자열을 리턴 |
start() | 매치된 문자열의 시작 위치를 리턴 |
end() | 매치된 문자열의 끝 위치를 리턴 |
span() | 매치된 문자열의 (시작, 끝)에 해당하는 tuple을 리턴 |
p = re.compile("[A-z]+") string = "HelloWorld" result = p.match(string) print(result) # Match
<_sre.SRE_Match object; span=(0, 10), match='HelloWorld'>
참고로 pattern에서 a와 A는 서로 다른 문자로, ASCII code값을 기준으로 결정되기 때문에 모든 영문자를 찾고 싶으면 [A-z]
로 해야한다.
2-3. search
위 예제 코드에서 만약 input string이 ” Hello “와 같이 띄어쓰기가 포함될 경우 앞 부분인 ” ” 공백을 읽고 None으로 매치가 안될 것이다. 이 경우엔 search를 통해 전체 문자열에서 찾도록 할 수 있다.
p = re.compile("[A-z]+") string = " Hello " result = p.match(string) print(result) # None result = p.search(string) print(result) # Match
2-4. findall
search
보다 좀 더 관대한 방식의 함수로, 모든 문자열에 대해서 pattern을 찾게 되는데 기존 search
의 경우 부합하는 경우를 찾게 되면 더 이상 찾지 않지만, findall
의 경우는 찾고 난 뒤에도 모든 문자열을 계속 찾는다.
참고로 return은 list 형식으로 출력시킨다.
2-5. finditer
findall
과 동일한 동작을 하지만 return type이 다른데, finditer
의 경우 iterable object를 return한다.
p = re.compile("[A-z]+") string = "This is Hello World" result = p.finditer(string) for i in result: print(i) # Match
$ python test.py <_sre.SRE_Match object; span=(0, 4), match='This'> <_sre.SRE_Match object; span=(5, 7), match='is'> <_sre.SRE_Match object; span=(8, 13), match='Hello'> <_sre.SRE_Match object; span=(14, 19), match='World'>
Reference
- https://wikidocs.net/4308