파이썬의 정규표현식에 대해 알아보자.
메타 문자란, 정규표현식에서 사용했을 때, 원래의 의미가 아닌 특별한 의미로 사용되는 아래 문자를 의미한다.
. ^ $ * + ? { } [ ] \ | ( )
[ ] 문자 - 문자 클래스
문자 클래스로 만들어진 정규식은 [
와 ]
사이의 문자들과 매치 라는 의미를 갖는다.
예를 들어, 표현식이 [abc]
인 경우, a b c 중 한 개의 문자와 매치
를 의미한다.
a
: [abc]
중 a
가 존재하므로, 매치된다.before
: [abc]
중 b
가 존재하므로, 매치된다.dude
: [abc]
중 어느 하나도 포함하고 있지 않으므로, 매치되지 않는다.[]
안에서 두 문자 사이에 -
을 사용하면, 두 문자 사이의 범위를 의미한다.
예를 들어, 아래와 같다.
[a-zA-Z]
: 모든 알파벳[0-9]
: 모든 숫자문자 클래스
[]
안에는 어떤 문자나 메타 문자를 사용할 수 있지만,^
을 사용할 때, 주의해야 한다.
^
은 반대(not)의 의미를 가져,[^0-9]
라는 정규 표현식은 숫자가 아닌 문자만 매치된다.
\n
을 제외한 모든 문자정규 표현식의 .
메타 문자는 줄바꿈 문자인 \n
을 제외한 모든 문자와 매치되는 것을 의미한다.
a.b
=> "a + 모든 문자 + b" , a와 b 문자 사이에 어떤 문자가 들어가도 모두 매치된다는 뜻
aab
: 가운데 존재하는 'a'가 모든 문자를 의미하는 .
과 일치하므로, 정규식과 매치a0b
: 가운데 존재하는 '0'이 모든 문자를 의미하는 .
과 일치하므로, 정규식과 매치abc
: a
와 b
사이에 어떤 문자라도 존재하지 않기 때문에, 매치되지 않음하지만, a[.]b
의 경우는 위와 다르게, 문자 그대로를 의미한다.
"a + . + b"
*
문자*
문자는 반복을 의미한다. *
바로 앞에 있는 문자 a
가 0부터 무한대까지 반복될 수 있다는 의미로 사용한다.
ca*t
ct
: "a"가 0번 반복되어 매치cat
: "a"가 1번 반복되어 매치caat
: "a"가 2번 반복되어 매치
+
문자*
문자가 최소 0번부터 사용할 수 있는거라면, +
는 반복 횟수가 1부터 시작한다.
ca+t
ct
: "a"가 0번 반복되어 매치되지 않음cat
: "a"가 1번 반복되어 매치caat
: "a"가 2번 반복되어 매치
{ }
, ?
문자반복횟수를 제한하고 싶을 때 { }
을 사용해 고정할 수 있다. {m, n}
정규식을 사용하면, 반복횟수가 m
부터 n
까지인 문자와 매치할 수 있다.
만약, {3,}
처럼 사용하면 반복 횟수가 3 이상인 경우이고, {, 3}
처럼 사용하면 반복 횟수가 3이하인 경우를 의미한다.
생략된 m
은 0과 동일하며, 생략된 n
은 무한대를 의미한다. (실제로는 약 2억개)
{1, }은 +와 같고, {0, }은 *와 동일하다.
{m}
-> ca{2}t
-> "c + a를 반드시 2번 반복 + t"{m, n}
-> ca{2, 5}t
-> "c + a를 2~5회 반복 + t"반복은 아니지만,
?
문자는 {0, 1}을 의미한다.
ab?c
-> "a + b가 있어도 되고, 없어도 된다 + c" => abc
, ac
둘 다 가능파이썬은 re(regular expression)
모델을 제공한다. 파이썬을 설치할 때 자동으로 설치되는 표준 라이브러리로, 다음과 같이 사용한다.
import re
p = re.compile('ab*') # re.compile을 사용하여, 정규표현식을 컴파일한다.
컴파일된 패턴 객체를 사용해 문자열 검색을 수행할 수 있다.
match()
: 문자열의 처음부터 정규식과 매치되는지 조사한다.search()
: 문자열 전체를 검색하여 정규식과 매치되는지 조사한다.findall()
: 정규식과 매치되는 모든 문자열(substring)을 리스트로 리턴한다.finditer()
: 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 리턴한다.\
match()
,search()
는 정규식과 매치될 때match
객체를 리턴하고, 매치되지 않을 때None
을 리턴한다.
import re
p = re.compile('[a-z]+')
m = p.match("python")
print(m)
>>> <re.Match object; span=(0, 6), match='python'> # python은 정규식에 부합
m = p.match("3 python") # 3이 부합하지 않아 None 리턴
print(m)
None
match()
의 결과로 match
객체 또는 None
을 리턴하기 때문에, 보통 다음과 같은 흐름으로 작성한다.
p = re.compile(정규표현식)
m = p.match('조사할 문자열')
if m: # match의 결과값이 존재하는 경우에만 다음 작업 수행
print('Match found: ', m.group())
else:
print('No match')
m = p.search("python")
print(m)
<re.Match object; span=(0, 6), match='python'>
m = p.search("3 python")
print(m)
<re.Match object; span=(2, 8), match='python'>
python 문자열에 search()
메서드를 수행하면, match()
메서드를 수행했을 때와 동일하게 매치된다.
'3 python' 문자열의 첫 번째 문자는 '3' 이지만, 'search'는 문자열의 처음부터 검색하는 것이 아니라 문자열 전체를 검색하기 때문에, '3' 이후의 'python' 문자열과 매치된다.
result = p.findall("life is too short")
print(result)
['life', 'is', 'too', 'short']
findall()
은 패턴 [a-z]+
과 매치되는 모든 값을 찾아 리스트로 리턴한다.
result = p.finditer("life is too short")
print(result)
<callable_iterator object at 0x01F5E390>
for r in result: print(r)
...
<re.Match object; span=(0, 4), match='life'>
<re.Match object; span=(5, 7), match='is'>
<re.Match object; span=(8, 11), match='too'>
<re.Match object; span=(12, 17), match='short'>
finditer
는 findall
과 동일하지만, 그 결과로 반복 가능한 객체 (iterator object)
를 리턴한다. 반복 가능한 객체가 포함하는 각각의 요소는 match
는 객체다.
match
객체란 앞에서 살펴본 p.match
, p.search
, p.finditer
메서드에 의해 리턴된 매치 객체(Match Object)를 의미한다.
group
: 매치된 문자열을 리턴한다.start
: 매치된 문자열의 시작 위치를 리턴한다.end
: 매치된 문자열의 끝 위치를 리턴한다.span
: 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 리턴한다.m = p.match("python")
m.group()
'python'
m.start()
0
m.end()
6
m.span()
(0, 6)
match
메서드를 수행한 결과로 리턴된 match
객체로 start
메서드를 사용했을 때, 결과값은 항상 0일 수 밖에 없다. match
메서드는 항상 문자열의 시작부터 조사하기 때문이다.
만약 search
메서드를 사용했다면, 다른 값이 나올 수 있다.
m = p.search("3 python")
m.group()
'python'
m.start()
2
m.end()
8
m.span()
(2, 8)
DOTALL(S)
: .(dot)이 줄바꿈 문자를 포함해 모든 문자와 매치될 수 있게 한다.IGNORECASE(I)
: 대소문자에 관계없이 매치될 수 있게 한다.MULTILINE(M)
: 여러 줄과 매치될 수 있게 한다. ^
, $
메타 문자 사용과 관계 있는 옵션이다.VERBOSE(X)
: verbose 모드를 사용할 수 있게 한다. 정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게 된다.옵션을 사용할 때는 re.DOTALL
처럼 전체 옵션 이름을 써도 되고, re.S
처럼 약어를 써도 된다.
.
메타 문자는 줄바꿈 문자 \n
를 제외한 모든 문자와 매치되는 규칙이 있다. 만약 \n
문자도 포함하여 매치하고 싶다면, re.DOTALL
, re.S
옵션을 사용해 정규식을 컴파일하면 된다.
import re
p = re.compile('a.b')
m = p.match('a\nb')
print(m)
None
정규식이 a.b
인 경우, 문자열 a\nb
는 매치되지 않는다는 것을 알 수 있다. \n
은 .
메타 문자와 매치되지 않기 때문이다. \n
문자와도 매치되게 하려면 다음과 같이 re.DOTALL
옵션을 사용해야 한다.
p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb')
print(m)
<re.Match object; span=(0, 3), match='a\nb'>
re.DOTALL
옵션은 여러 줄로 이루어진 문자열에서 줄바꿈 문자에 상관없이 검색할 때 많이 사용한다.
re.IGNORECASE
또는 re.I
옵션은 대소문자 구별 없이 매치를 수행할 때 사용하는 옵션이다.
p = re.compile('[a-z]+', re.I)
p.match('python')
<re.Match object; span=(0, 6), match='python'>
p.match('Python')
<re.Match object; span=(0, 6), match='Python'>
p.match('PYTHON')
<re.Match object; span=(0, 6), match='PYTHON'>
[a-z]+
정규식은 소문자만을 의미하지만, re.I
옵션으로 대소문자 구별 없이 매치된다.
re.MULTILINE
, re.M
옵션은 ^
, $
와 연관된 옵션이다.
^
는 문자열의 처음, $
는 문자열의 마지막을 의미한다.
예를 들어, ^python
인 경우, 문자열의 처음은 항상 python
으로 시작해야 매치하고, python$
이라면, 문자열의 마지막은 항상 python
으로 끝나야 매치된다.
# multiline.py
import re
p = re.compile("^python\s\w+")
data = """python one
life is too short
python two
you need python
python three"""
print(p.findall(data))
정규식 ^python\s\w+
은 python
이라는 문자열로 시작하고, 그 뒤에 화이트스페이스, 그 뒤에 단어가 와야 한다는 의미다.
해당 스크립트를 실행하면, 다음과 같은 결과를 리턴한다.
['python one']
^
메타 문자에 의해 python
이라는 문자열을 사용한 첫 번째 줄만 매치된 것이다.
하지만 ^
메타 문제를 문자열 전체의 처음이 아니라, 각 라인의 처음으로 인식시키고 싶은 경우에는 re.MULTILINE
, re.M
을 활용한다.
# multiline.py
import re
p = re.compile("^python\s\w+", re.MULTILINE)
data = """python one
life is too short
python two
you need python
python three"""
print(p.findall(data))
re.MULTILINE
옵션으로 인해 ^
메타 문자가 문자열 전체가 아닌 각 줄의 처음이라는 의미를 가지게 되었다.
['python one', 'python two', 'python three']
즉, re.MULTILINE
옵션은 ^
, $
메타 문자를 문자열의 각 줄마다 적용해 주는 것이다.
charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')
어려운 정규식을 주석 단위, 줄 단위로 구분하기 위해 re.VERVOSE
, re.X
옵션을 사용한다.
charref = re.compile(r"""
&[#] # Start of a numeric entity reference
(
0[0-7]+ # Octal form
| [0-9]+ # Decimal form
| x[0-9a-fA-F]+ # Hexadecimal form
)
; # Trailing semicolon
""", re.VERBOSE)
re.VERBOSE
옵션을 사용하면, 문자열에 사용된 화이트스페이스는 컴파일할 때 제거된다.
정규표현식을 파이썬에서 사용할 때 혼란을 주는 요소가 1가지 존재하는데, 바로 역슬래시(\
)다.
어떤 파일안에 있는 \section
문자열을 찾기 위한 정규식을 만든다고 가정해본다.
\section
이 정규식은 \s
문자가 whitespace
로 해석되어 의도한 대로 매치가 이루어지지 않는다.
이 표현은 다음과 동일한 의미로 사용할 수 있다.
[ \t\n\r\f\v]ection
의도한대로 매치하고 싶다면, 다음과 같이 변경해야 한다.
\\section
즉, 앞 정규식에서 사용한 \
문자가 문자열 자체라는 것을 알려주기 위해 역슬래시 2개를 사용해 이스케이프 처리를 해야 한다.
해당 정규식을 컴파일하려면 다음과 같이 작성해야 한다.
p = re.compile('\\section')
이처럼 정규식을 만들어서 컴파일하면, 실제 파이썬 정규식 엔진에는 파이썬 문자열 리터럴 규칙에 따라, \\
이 \
로 변경되어 \section
이 전달된다.
결국, 아래와 같이 역슬래시 4개를 사용해야 하는 상황이 발생한다.
p = re.compile('\\\\section')
이를 해결하기 위해 raw string
을 사용한다.
p = re.compile(r'\\section')
이와 같이 정규식 문자열 앞에 r
문자를 삽입하면, 이 정규식은 raw string
규칙에 의해 역슬래시 2개 대신 1개만 써도 2개를 쓴 것과 동일한 의미를 가지게 된다.