이 문서는 점프 투 파이썬을 기본 바탕으로 작성되었다.(복사하여 붙여 넣었다고 읽는다.)
추후 학습하거나 깨닳은 요령이 생긴다면 최신화하는 방향으로 한다.
특정한 규칙
을 가진 문자열의 집합을 표현하는 데 사용하는 형식 언어 컴퓨터 과학의 정규 언어로부터 유래하였으나 구현체에 따라서 정규 언어보다 더
넓은 언어를 표현할 수 있는 경우도 있으며, 심지어 정규 표현식 자체의 문법도
여러 가지 존재하고 있다.
코딩을 하다보면 문자열을 조작해야 하는 경우가 생긴다. 이 때 문자열 자료형의 기능이나 내장 함수를 통해 수정하거나 관리할 수 있겠지만 특정한 단어를 찾아 바꾸거나 일정 패턴의 문자열을 제거한다던가 하는 작업엔 어려움이 따른다. 이를 해결하기 위해 정규표현식(정규식)을 사용하게 된다.
. ^ $ * + ? { } [ ] \ | ( )
-
^
\d
\D
\s
\S
\w
\W
[ ] 사이의 문자들과 매치
의 의미 $$ 정규식 [abc]와 문자열과의 매치 $$
1. "a" : 정규식과 일치하는 문자 "a"가 있으므로 매치 O
2. "before" : 정규식과 일치하는 문자인 "b"가 있으므로 매치 O
3. "dude" : 정규식과 아무것도 일치하는 것이 없으므로 매치되지 않음 X
-
을 사용하면 두 문자 사이의 범위를 의미 1. [a-zA-Z] : a ~ z 까지 그리고 A ~ Z 까지 모든 알파벳을 포함
2. [0-9] : 0 ~ 9 까지의 모든 숫자
^
메타 문자를 사용하면 반대(not)의 의미를 갖는다. 1. [^0-9] : 숫자가 아닌 문자만 매치
$$ 자주 사용하는 문자 클래스 $$
1. \d : 숫자와 매치, [0-9]와 동일
2. \D : 숫자가 아닌 것과 매치, [^0-9]와 동일
3. \s : whitespace(공백문자)와 매치, [ \t\n\r\f\v]와 동일(맨 앞은 빈 칸(' ')의 공백 문자)
4. \S : whitespace가 아닌 것과 매치, [^ \t\n\r\f\v]와 동일
5. \w : 문자 + 숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일
6. \W : 문자 + 숫자(alphanumeric)이 아닌 문자와 매치, [^a-zA-Z0-9_]와 동일
대문자는 소문자 표현식의 반대임을 추측할 수 있다.
'.'
.
: 줄바꿈 문자인 \n
을 제외한 모든 문자와 매치\n
문자와도 매치할 수 있다.) a.b = "a + 모든문자 + b"
1. "aab" : "a + a(모든문자) + b"이므로 매치 O
2. "a0b" : "a + 0(모든문자) + b"이므로 매치 O
3. "abc" : "a"와 "b"사이에 어떤 문자라도 하나는 있어야 하지만 없으므로 매치되지 않음 X
.
만 존재한다면 모든 문자라는 의미가 아닌 .
그대로를 의미 a[.]b = "a + . + b"
1. "a.b" : "a + . + b"이므로 매치 O
2. "a0b" : "a + 0(. 아님) + b"이므로 매치되지 않음 X
'*'
*
: 반복을 의미 *
바로 앞에 있는 문자가 0개부터 무한대로 반복될 수 있음 ca*t
1. "ct" : "a"가 0번 반복되므로 매치 O
2. "cat" : "a"가 0번 이상 반복되므로 매치(1번) O
3. "caaat" : "a"가 0번 이상 반복되므로 매치(3번) O
'+'
+
: 반복을 의미 +
바로 앞에 있는 문자가 1개부터 무한대로 반복 ca+t
1. "ct" : "a"가 0번 반복되므로 매치 X
2. "cat" : "a"가 1번 이상 반복되므로 매치(1번) O
3. "caaat" : "a"가 1번 이상 반복되므로 매치(3번) O
?
m
번부터 n
번까지 반복m
또는 n
을 생략할 수 있다.) ca{2}t
1. "cat" : "a"가 2번 반복되지 않으므로 매치 X
2. "caat" : "a"가 2번 반복되므로 매치 O
ca{2,5}t
1. "cat" : "a"가 1번만 반복되어 매치 X
2. "caat" : "a"가 2번 반복되어 매치 O
3. "caaaaat" : "a"가 5번 반복되어 매치 O
'?'
?
: 반복은 아니지만 비슷한 개념으로 {0,1}을 의미 ab?c
1. "abc" : "b"가 1번 사용되어 매치 O
2. "ac" : "b"가 0번 사용되어 매치 O
'^','$'
^
: 문자열의 시작으로부터 정규식을 만족해야 함을 의미 ^https:[a-z]+[.][a-z]+
1. "http:google.com" : "http:"에서 "s"가 없기 때문에 매치 X
2. "https:google.com" : "https:"가 맞고 뒤의 정규식을 만족하므로 매치 O
$
: 문자열의 끝을 의미 [a-z]+ done[.]$
1. "python is fun done" : "done"의 "."가 없기 때문에 매치 X
2. "python is fun done." : 앞의 정규식을 만족하며 "done."을 만족하기 때문에 매치 O
'|'
|
: or
와 동일한 의미로 사용된다. A|B
는 A
또는 B
라는 의미p = re.compile('Crow|Servo')
m = p.amatch('CrowHello')
print(m)
>> <re.Match object; span=(0, 4), match='Crow'>
'\A','\Z'
\A
: ^
와 동일하게 문자열의 처음과 매치됨을 의미한다.p = re.compile("\Apython\s\w+", re.MULTILINE)
data = """python one
life is too short
python two
you need python
python three"""
print(p.findall(data))
// 이후 등장하는 MULTILINE(각 줄별로 매치함)를 사용해도 전체 문자열의 처음만 매치됨 //
>> ['python one']
\Z
: $
와 동일하게 문자열의 마지막과 매치된다.\A
와 마찬가지로 MULTILINE 옵션 사용 시에도 전체 문자열로부터 조사한다.)p = re.compile("\w+\spython\Z", re.MULTILINE)
data = """first python
life is too short
second python
you need python
third python"""
print(p.findall(data))
>> ['third python']
'\b', '\B'
\b
: 단어 구분자(Word boundary)이다. 단어는 whitespace에 의해 구분된다.\b
는 파이썬 리터럴 규칙에 의하면 백스페이스(BackSpace)를 의미하므로 단어 구분자임을 알려주기 위해 r'\bclass\b'
처럼 Raw string으로 작성해야 한다.)p = re.compile(r'\bclass\b')
// 양 쪽이 whitespace(공백문자)이며 "class"가 있으므로 매치 O //
print(p.search('no class at all'))
>> <re.Match object; span=(3, 8), match='class'>
// "class"가 있지만 양 쪽이 whitespace가 아니므로 매치 X //
print(p.search('the declassified algorithm'))
>> None
// "class"가 있지만 한 쪽이 whitespace가 아니므로 매치 X //
print(p.search('one subclass is'))
>> None
\B
: \b
와 반대의 경우이다. whitespace로 구분되지 않은 단어를 매치한다.p = re.compile(r'\Bclass\B')
print(p.search('no class at all'))
>> None
print(p.search('the declassified algorithm'))
>> <re.Match object; span=(6, 11), match='class'>
print(p.search('one subclass is'))
>> None
( )(하위식)
, (?...)
: (?P...)
(?=...)
(?!...)
( )
(하위식)( )
: ( )
안의 문자열이 반복되는지 조사p = re.compile('(ABC)+')
m = p.search('ABCABCABC OK?')
print(m)
>> <re.Match object; span=(0, 9), match='ABCABCABC'>
print(m.group())
>> ABCABCABC
()
를 사용하는 이유는 반복되는 문자 이외에도 매치된 문자열에서 특정 부분의 문자열만 뽑아내기 위해서이다.// '( )'을 사용하지 않은 경우 //
p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")
m = p.search("park 010-1234-1234")
// 기본적으로 매치된 문자열을 돌려준다. //
print(m.group())
>> park 010-1234-1234
// 그룹화된 문자열이 없기 때문에 인덱스에러가 발생한다. //
print(m.group(1))
>> IndexError: no such group
// '( )'를 사용하여 grouping한 경우 //
p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
m = p.search("park 010-1234-1234")
// 기본적으로 매치된 문자열을 돌려준다. //
print(m.group())
>> park 010-1234-1234
// '\w+'를 '(\w+)'로 grouping 해주었기 때문에 //
// group의 첫 번째 인덱스가 문자열인 "park"를 나타낸다. //
print(m.group(1))
>> park
group 메서드의 인덱스
group(인덱스) | 설명 |
---|---|
group(0) | 매치된 전체 문자열 |
group(1) | 첫 번째 그룹에 해당되는 문자열 |
group(2) | 두 번째 그룹에 해당되는 문자열 |
group(n) | n 번째 그룹에 해당되는 문자열 |
p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
// 전화번호 부분을 grouping하여 group으로 뽑아내는 경우 //
print(m.group(2))
>> 010-1234-1234
// 그룹을 중첩 사용하는 것도 가능하다. //
// 중첩 시는 바깥쪽부터 시작하여 인덱스가 증가한다. //
print(m.group(3))
>> 010
( )\1
: ( )
로 그룹화된 문자열은 \n(1번 부터 시작하는 인덱스)
메타 문자로 정규식 내에서 불러 사용할 수 있다.// '(\b\w+)'를 `\1`로 재참조하여 사용하므로써 //
// "(group) + ' ' + group과 매치된 단어"와 매치 //
p = re.compile(r'(\b\w+)\s+\1')
str = p.search('Paris in the the spring').group()
// '(\b\w+)' == 'the', '\1' == 'the' //
print(str)
>> the the
// 여러 문자를 그룹화(grouping)했다면 `\n`을 통해 n 번째의 그룹을 참조할 수 있다. //
(?...)
(?...)
: 정규표현식의 확장 구문으로, 강력한 기능들을 사용할 수 있다.구문 | 설명 |
---|---|
(?P<그룹명>...) | '...'에 매치되는 문자열이 '그룹명'을 가짐 |
(?P=그룹명) | '그룹명'의 값을 참조(정규식 내에서 재참조 시 사용) |
(?=...) | '...'을 포함하여 매치되는 문자열을 검색하지만 검색 결과에는 포함시키지 않음 |
(?!...) | '...'을 포함하는 문자열의 경우 결과에서 제외 |
(?P<name>...)
(?P<그룹명>...)
: ( )
내에서 ?P<그룹명>
구문을 사용하면 이후 정규식에 매치되는 문자열을 해당 이름(변수명)으로 그룹화(grouping)할 수 있다.그룹명
은 정규식 안에서 재참조가 가능하다.)// '(?P<name>\w+)' => '(\w+)'에 "name"의 그룹명을 붙임 //
p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
m = p.search("park 010-1234-1234")
// "name"의 그룹명을 가진 "park"를 결과로 돌려준다. //
print(m.group("name"))
>> park
// "word"를 정규식 내에서 재참조 //
p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
str = p.search('Paris in the the spring').group()
// (?P<word>\b\w+) => word = 'the', (?P=word) => 'the' //
print(str)
>> the the
(?=...)
(?=...)
: ...
를 포함하여 매치하지만 결과에서 ...
를 제외// (?=:) => ':'을 포함하여 검색하지만 결과에서 제외 //
p = re.compile(".+(?=:)")
m = p.search("http://google.com")
// '.+(?=:)' => http + ':' = "http" 제외 //
print(m.group())
>> http
(?!...)
(?!...)
: ...
의 매치 결과를 포함하는 결과를 제외(?=...)
)에서 작성이 어려운 정규식을 쉽게 사용할 수 있다.)// (?!bat$|exe$) => "bat"이나 "exe"끝나는 것들은 제외 //
p = re.compile(r".*[.](?!bat$|exe$).*$", re.MULTILINE)
s = """data.db
python.exe
title.txt
seq.bat
server.js
"""
m = p.findall(s)
// "python.exe"와 "seq.bat"가 제외된 결과 //
print(m)
>> ['data.db', 'title.txt', 'server.js']
import re
p = re.compile('ab*') // 'ab*'를 컴파일한다
re.compile을 사용하여 정규표현식을 컴파일하고 돌려주는 객체(p)를 사용하여 이후 작업을 수행하게 된다.
// 메서드 사용을 위한 패턴 //
import re
p = re.compile('[a-z]+')
m = p.match("python")
print(m)
>> <re.Match object; span=(0, 6), match='python'>
[a-z]+
에 부합하므로 match
객체를 돌려준다.m = p.match("3 python")
print(m)
>> None
[a-z]+
에 부합하지 않으므로 None
을 돌려준다.p = re.compile(정규식)
m = p.match('string goes here')
// match의 결과값이 있을 경우만 작업을 수행 //
if m:
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'>
result = p.findall("life is too short")
print(result)
>> ['life', 'is', 'too', 'short']
result = p.finditer("life is too short")
print(result)
>> <callable_iterator object at 0x000002AF97B74E50>
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'>
method | 목적 |
---|---|
group() | 매치된 문자열을 돌려준다 |
start() | 매치된 문자열의 시작 위치를 돌려준다 |
end() | 매치된 문자열의 끝 위치를 돌려준다 |
span() | 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다. |
import re
p = re.compile('[a-z]+')
m = p.match("python")
m.group()
>> 'python'
m.start() // match 메서드는 시작부터 조사하기 때문에 start는 항상 0이다.
>> 0
m.end()
>> 6
m.span()
>> (0, 6)
m = p.match("3 python")
m.group()
>> 'python'
m.start()
>> 2
m.end()
>> 8
m.span()
>> (2, 8)
[모듈 단위로 수행하기]
지금까지
re.compile
을 사용하여 컴파일된 패턴 객체를 사용했다면
re 모듈은 보다 축약한 형태로 사용할 수 있는 방법을 제공한다.// 기본적인 컴파일된 패턴과 매치된 객체 // p = re.compile('[a-z]+') m = p.match("python") // 모듈 내에서 축약시켜 사용하는 예 // m = re.match('[a-z]+', "python")
컴파일과 메서드를 한 번에 수행할 수 있지만, 한 번 만든 패턴 객체를
여러번 사용해야 할 때는re.compile
을 사용하는 것이 편하다.
re.DOTALL
처럼 전체 옵션을 사용하거나 re.S
처럼 약어를 사용할 수 있다.)option(약어) | 기능 |
---|---|
DOTALL(S) | '.' 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다 |
IGNORECASE(I) | 대소문자에 관계없이 매치할 수 있도록 한다 |
MULTILINE(M) | 여러줄과 매치할 수 있도록 한다('^' ,'$' 메타문자의 사용과 관계가 있는 옵션이다) |
VERBOSE(X) | verbose 모드를 사용할 수 있도록 한다(정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게된다) |
.
사용 시 줄바꿈 문자(\n
)도 포함하여 매치한다.import re
// 기본 컴파일 시 //
p = re.compile('a.b')
m = p.match('a\nb')
print(m)
>> None
// DOTALL 옵션 사용 시 //
p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb')
print(m)
>> <re.Match object; span=(0, 3), match='a\nb'>
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'>
^
, $
와 연관된 옵션으로, ^
또는 $
옵션을 각 라인별로 적용시키고 싶을 경우 사용한다.\n
)가 있는 경우에 사용한다.)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 one']
// MULTILINE 옵션을 이용한 정규식 매치 //
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))
>> ['python one', 'python two', 'python three']
[]
안에 사용한 whitespace는 제외된다.)// VERBOSE 적용 전 정규식 //
charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')
// VERBOSE 적용 후 사용 가능한 정규식 //
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)
\
는 혼란을 준다.// "\section"문자열을 찾기 위한 정규식 //
\section
==> '\s'문자가 whitespace로 해석되어 의도한 매치 불가
\section : [ \t\n\r\f\v]ection 과 동일
// '\'를 문자로 인식하게 하기 위해 '\\'(두 개를 사용) 이스케이프 처리를 한다. //
\\section
==> 파이썬 정규식 엔진에선 문자열 리터럴 규칙에 따라 '\\'이 '\'로 변경된다.
\\section : \section으로 변경됨(파이썬 문자열 리터럴 규칙에 따라)
(이 문제는 파이썬에서만 발생한다.)
// '\\'를 문자로 전달하기 위해선 '\\\\'를 사용해야 한다. ==> 비효율 //
// 파이썬 정규식의 'Raw String'규칙을 이용하여 처리할 수 있다. //
'\\\\section' == r'\\section'
==> ex) p = re.compile(r'\\section')
// 백슬래시를 사용하지 않는 정규식이라면 'r'의 유무에 상관없이 동일한 정규식이 된다. //
re.sub(바꿀 문자열, 대상 문자열, count=n(바꿀 횟수))
: 대상 문자열
에서 컴파일된 패턴에 해당하는 문자를 바꿀 문자열
로 바꾼다. count
가 있다면 그 횟수만큼만 바꾼다.p = re.compile('(blue|white|red)')
s = p.sub('colour', 'blue socks and red shoes')
// "blue", "red" => "colour" //
print(s)
>> 'colour socks and colour shoes'
// 'count'를 사용한 경우 //
s = p.sub('colour', 'blue socks and red shoes', count=1)
// 첫 번째인 "blue"만 "colour"로 바뀜 //
print(s)
>> 'colour socks and red shoes'
sub()
를 사용할 때 참조구문을 사용할 수 있다.// '그룹명'을 통한 참조와 sub() 활용 //
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
// '\g<그룹명>'을 통해 sub()에서 사용 가능하다. //
print(p.sub("\g<phone> \g<name>", "park 010-1234-1234"))
>> 010-1234-1234 park
// 그룹 '인덱스'를 통한 참조와 sub() 활용 //
p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
print(p.sub("\g<2> \g<1>", "park 010-1234-1234"))
>> 010-1234-1234 park
sub()
의 첫 번째 매개변수로 함수를 넣을 수 있다.// 10진수를 16진수로 바꾸는 함수 //
def hexrepl(match):
value = int(match.group())
return hex(value)
// sub()의 매개변수로 함수를 넘겨 10진수를 16진수로 변환 //ㅁ
p = re.compile(r'\d+')
s = p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
print(s)
>> 'Call 0xffd2 for printing, 0xc000 for user code.'
// "<html>"만을 매치하기 어렵다 //
s = '<html><head><title>Title</title>'
print(len(s))
>> 32
print(re.match('<.*>', s).span())
>> (0, 32)
print(re.match('<.*>', s).group())
>> <html><head><title>Title</title>
?
(Non-Greedy) : ?
non-greedy 문자로 가능한 한 최소한의 반복을 수행하도록 도움s = '<html><head><title>Title</title>'
print(re.match('<.*?>', s).group())
>> <html>
print(re.findall('<.*?>', s))
>> ['<html>', '<head>', '<title>', '</title>']