파이썬의 정규표현식에 대해 알아보자.
메타 문자란, 정규표현식에서 사용했을 때, 원래의 의미가 아닌 특별한 의미로 사용되는 아래 문자를 의미한다.
. ^ $ * + ? { } [ ] \ | ( )
[ ] 문자 - 문자 클래스문자 클래스로 만들어진 정규식은 [와 ] 사이의 문자들과 매치 라는 의미를 갖는다.
예를 들어, 표현식이 [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개를 쓴 것과 동일한 의미를 가지게 된다.