복잡한 문자열을 처리할 때 사용하는 기법으로 파이썬 뿐만 아니라 다른 모든 곳에서 자주 사용된다.
메타 문자
메타 문자란 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자를 말한다.
문자 클래스 [ ]
문자 클래스로 만들어진 정규식은 "[ ] 사이의 문자들과 매치" 라는 의미를 갖는다. 이 때 [] 사이에는 어떤 문자도 들어갈 수 없다.
이 때 하이폰(-)을 사용하면 두 문자 사이의 범위를 의미한다. (^)은 부정으로 반대를 의미한다.
(1) a
(2) bee
(3) dot
-1- [ a b c ] : (1)은 정규식과 일치하는 'a'가 있으므로 매치 , (2)도 'b'로 인해 매치 (3) 은 포함하는 문자가 없어 매치 X
-2- [ a-zA-z ] : 알파벳 모두 해당하므로 (1) (2) (3) 모두 매치
-3- [^0-9] : 숫자가 아닌 모든 표현식이므로 문자인 (1) - (3) 모두 매치
[ 자주 사용하는 문자 클래스 ] [0-9] 또는 [a-zA-Z] 등은 무척 자주 사용하는 정규 표현식이다. 자주 사용하는 정규식은 별도의 표기법으로 표현할 수 있다.
|
DOT (.)
줄바꿈 문자인 \n을 제외한 모든 문자와 매치된다. ( re.DOTALL 옵션시 \n 문자와도 매치될 수 있다. )
Dot을 사용한 정규식 a.b는 " a + 모든문자 + b " 와 의미가 같다. 즉 a-b사이 어떤 문자가 들어간다고 해도 매치된다.
a.b = 'aab' = 'adfafb' != 'abc'
이 때 'a.b'처럼 "." 자체를 매칭시키고 싶다면 a[.]b 처럼 정규식을 표현하면 "a.b"와는 매칭되고 그 사이 다른 문자들에 대해서는 매치되지 않는다.
반복 (*)
* 는 반복 개수를 무한대로 허용한다.
a * t 를 표현식으로 사용하면 앞에 'a'가 0부터 무한대까지 반복하여도 매칭된다. 중요한 점은 0이 포함이기 때문에 a가 없어도 문자열이 매칭된다.
'ca*t' == 'ct' == 'caaaaaat'
반복 (+)
이와 비슷하게 +는 반복을 표현하지만 최소 1번 이상 앞의 문자가 등장해야 매칭이된다 .
a + t 를 표현식으로 사용하면 앞에 'a'가 1부터 무한대까지 반복하여도 매칭된다.
'ca*t' == 'caaaaaat' != 'ct'
선택 반복 ( { m , n } , ? )
{ } 메타 문자를 사용하면 반복횟수를 선택하여서 매칭시킬 수 있다.
{ m, n }. 이라고 한다면 m부터 n사이 안으로 반복하는 문자들에 대해서 매칭된다.
'ca{2}" == 'caat' != 'cat' // 'c' + a(반드시 2번 반복) + 't'
'ca{2,5}' == 'caat' == 'caaaat' != 'cat' // 'c' + a(2-5회 반복) + 't'
?
? 문자는 반복에서 { 0, 1 } 을 의미한다
a b ? c 라고 한다면 'a' + b(있어도 되고 없어도 되고) + 'c'에 대해서 매칭된다.
'ab?c' == 'abc' == 'ac'
re 모듈
파이썬은 정규 표현식을 지원하기 위해 re ( regular expression) 모듈을 제공한다. re 모듈은 파이썬에서 기본 라이브러리로 제공되고 있다.
>>> import re >>> p = re.compile('ab*') |
라이브러리의 사용 방법은 위와 같다.
re.compile을 사용하여 정규 표현식(위 예에서는 ab*)을 컴파일한다.
re.compile의 결과로 돌려주는 객체 p(컴파일된 패턴 객체)를 사용하여 그 이후의 작업을 수행할 것이다.
( 패턴 = 정규식을 컴파일한 결과 )
패턴 객체에서는 4가지의 메서드를 제공하고 있다.
- match () : 문자열의 처음부터 정규식과 매치되는지 조사
- search () : 문자열 전체를 검색하여 정규식과 매치되는지 조사
- findall () : 정규식과 매치되는 모든 문자열을 리스트 형식으로 반환
- finditer() : 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 반환
# match() , search() 경우 매치되면 match()객체를 반환하고 매치되지 않는다면 None을 반환한다.
아래 예시를 위해 먼저 문자열을 포함하는 정규식 패턴을 만들어보자!
>>> import re >>> p = re.compile('[a-z]+') |
match
- 문자열의 처음부터 정규식과 매치되는지 조사
>>> m = p.match("python") # 처음 문자열로 매칭 성공 match() 객체 반환 >>> print(m) <_sre.SRE_Match object at 0x01F3F9F8> |
>>> m = p.match("3 python") # 처음에 숫자가 포함되어 매칭 실패 None 반환 >>> print(m) None |
위와 같이 매칭 성공/실패 여부에 따라 match()객체나 None을 반환하기 때문에 정규식 프로그래밍은 주로 다음과 같은 흐름을 가진다. p = re.compile(정규표현식) m = p.match( 'string goes here' ) if m: print('Match found: ', m.group()) else: print('No match') # 즉 match의 결괏값이 있을 때만 그다음 작업을 수행하겠다는 것이다. |
search
문자열 처음부터의 검색이 아닌 문자열 전체를 검색
>>> m = p.search("python") # 문자열로 매칭 성공 match() 객체 반환 >>> print(m) <_sre.SRE_Match object at 0x01F3F9F8> |
>>> m = p.search("3 python") # 문자열 전체를 검색하기 때문에 처음에 3이 등장해도 매치된다/ >>> print(m) <_sre.SRE_Match object at 0x01F3F930> |
findall
해당하는 정규식과 매칭된 요소를 리스트로 반환한다.
>>> result = p.findall("life is too short") >>> print(result) ['life', 'is', 'too', 'short'] |
"life is too short" 문자열의 'life', 'is', 'too', 'short' 단어를 각각 [a-z]+ 정규식과 매치해서 리스트로 돌려준다.
finditer
finditer는 반복 가능한 객체(iterator object)를 반환한다. 반복 가능한 객체가 포함하는 각각의 요소는 match 객체이다.
>>> result = p.finditer("life is too short") >>> print(result) <callable_iterator object at 0x01F5E390> >>> for r in result: print(r) ... <_sre.SRE_Match object at 0x01F3F9F8> <_sre.SRE_Match object at 0x01F3FAD8> <_sre.SRE_Match object at 0x01F3FAA0> <_sre.SRE_Match object at 0x01F3F9F8> |
match 객체의 메서드
수행한 패턴에 대한 반환값을 match 객체의 내부 메서드를 통해서 원하는 정보로 반환값을 받을 수 있다.
group() | 매치된 문자열을 돌려준다. |
start() | 매치된 문자열의 시작 위치를 돌려준다. |
end() | 매치된 문자열의 끝 위치를 돌려준다. |
span() | 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다. |
>>> m = p.match("python") >>> m.group() 'python' >>> m.start(). // 항상 0 : match는 시작부터 조사하므로 0 >>> m.end() 6 >>> m.span() (0, 6) |
>>> m = p.search("3 python") >>> m.group() 'python' >>> m.start() 2 >>> m.end() 8 >>> m.span() (2, 8) |
[모듈 단위로 수행하기]
re 모듈을 통해서 표현식의 컴파일과 매칭 작업을 함께 사용할 수 있는데 아래와 가다
>>> p = re.compile('[a-z]+')
>>> m = p.match("python")
== 이를 re 모듈을 통해 줄인다면 아래와 같아진다 ==
>>> m = re.match('[a-z]+', "python")
위 예처럼 사용하면 컴파일과 match 메서드를 한 번에 수행할 수 있다. 패턴객체를 여러번 사용 할 때는 이 방법보다 re.compile을 사용하는 것이 편하다.
컴파일 옵션
정규식을 compile하는 경우 지정된 옵션을 통해서 제약사항을 줄 수 있게 한다.
- DOTALL(S) -" . " 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다. ( re.S)
- IGNORECASE(I) - 대소문자에 관계없이 매치할 수 있도록 한다. (re.I)
- MULTILINE(M) - 여러줄과 매치할 수 있도록 한다. 각 라인 적용 (^, $ 메타문자의 사용과 관계가 있는 옵션이다. (re.M)
- VERBOSE(X) - verbose 모드를 사용할 수 있도록 한다. (정규식을 보기 편하게 만들수 있고 주석등을 사용할 수 있게된다. (re.X)
옵션을 사용할 때는 re.DOTALL처럼 전체 옵션 이름을 써도 되고 re.S처럼 약어를 써도 된다.
백슬레시 문제
정규식에서는 \ 를 문자열로 받아 들이기 위해 백슬레시를 2개 사용하여 이스케이프 처리를 한다. '. \\. "
그런데 파이썬에서 정규식을 만들어 컴파일하면 실제 파이썬 정규식 엔진에서는 파이썬 문자열 리터럴 규칙에 따라 \\ 이 \ 로 변경되어 결국 \section 에 대해서 컴파일로 전달된다.
때문에 정규식 엔진에 \\ 문자를 전달하기 위해 파이썬에서는 \\\\ 처럼 백슬레시를 4개 사용한다.
이를 해결하기 위해 파이썬 정규식에서는 Raw string 규칙 ( R-string)이 생겨나게 되었다.
.>>> p = re.compile(r'\\section')
위와 같이 정규식 문자열 앞에 r 문자를 삽입하면 이 정규식이 규칙에 의해 \\ 를 \ 쓴 것으로 판단하여 처리하게 된다.