정규표현식(RegEx, Regular Expressions)은 특정한 규칙을 가진 문자열의 집합을 설명하는 패턴. RegEx는 문자열에 특정 패턴이 포함되어 있는지 확인하는 데 사용할 수 있음.
Python에는 re라는 패키지가 내장.
import re
RegEx는 일치하는 문자열을 검색할 수 있는 기능을 제공함.
| Function | Description |
|---|---|
findall | 모든 일치하는 항목을 포함하는 리스트를 반환 |
search | 문자열 어디에나 일치하는 항목이 있으면 Match 객체를 반환 |
split | 각 일치하는 항목에서 문자열을 분할하여 리스트를 반환 |
sub | 하나 또는 여러 일치하는 항목을 문자열로 교체 |
findall() 함수는 모든 일치 항목을 포함하는 리스트를 반환. 목록에는 일치하는 항목이 검색된 순서대로 들어있음.
import re
txt = "The rain in Spain"
x = re.findall("ai", txt) # re.findall(pattern, string, flags=0)
print(x)
['ai', 'ai']
만약 일치하는 항목이 없으면 빈 리스트가 반환됨.
# 일치하는 항목이 없는 경우
import re
txt = "The rain in Spain"
x = re.findall("Portugal", txt)
print(x)
[]
search() 함수는 문자열에서 일치하는 항목을 검색하고 일치하는 항목이 있으면 Match 객체를 반환. 일치하는 항목이 2개 이상이여도, 첫번재 항목만 반환됨.
import re
txt = "The rain in Spain"
x = re.search("ai", txt) # re.search(pattern, string, flags=0)
print(x) # 찾은 문자를 표시X. 일치하는 객체 x에 대한 정보를 표시. 일치하는 시작 및 끝 인덱스가 포함.
print("첫번째 'ai'의 위치: ", x.start()) # 일치하는 문자열의 시작 위치를 반환.
print("일치하는 부분: ", x.group())
<re.Match object; span=(5, 7), match='ai'>
첫번째 'ai'의 위치: 5
일치하는 부분: ai
일치하는 항목이 없는경우 None 반환됨.
# 일치하는 항목이 없는 경우
import re
txt = "The rain in Spain"
x = re.search("Portugal", txt)
print(x)
None
split() 함수는 각 일치에서 문자열이 분할된 리스트를 반환.
import re
txt = "The rain in Spain"
x = re.split("ai", txt) # re.split(pattern, string, maxsplit=0, flags=0)
print(x)
['The r', 'n in Sp', 'n']
만약 일치되는 문자열이 없으면 분할되지 않아서 기존 문자열을 그대로 반환.
# 일치하는 항목이 없는 경우
import re
txt = "The rain in Spain"
x = re.split("Portugal", txt)
print(x)
['The rain in Spain']
maxsplit 파라미터를 지정하여 분할 횟수를 제어할 수 있음.
import re
txt = "The rain in Spain"
x = re.split("ai", txt, 1)
print(x)
['The r', 'n in Spain'] # 앞에 'ai'만 제외됨.
sub() 함수는 일치 항목을 선택한 텍스트로 대체할 수 있음.
import re
txt = "The rain in Spain"
x = re.sub("ai", "9", txt) # re.sub(pattern, repl, string, count=0, flags=0)
print(x)
The r9n in Sp9n
만약 일치하는 항목이 없으면 바뀌지 않아서 긱존 문자열 그대로 출력.
import re
txt = "The rain in Spain"
x = re.sub("Portugal", "9", txt)
print(x)
The rain in Spain
count 파라미터를 지정하여 교체 횟수를 제어할 수 있다.
import re
txt = "The rain in Spain"
x = re.sub("ai", "9", txt, 1)
print(x)
The r9n in Spain
위에서 serach()함수가 Match Object를 반환했다. Match Object는 검색 및 결과에 대한 정보를 포함하는 객체. 일치하는 값이 없으면 None이 반환됨.
.span()는 일치항목의 시작 위치와 끝 위치가 들어 있는 튜플을 반환.
.string는 함수에 전달된 문자열을 반환.
.group()는 일치하는 문자열의 부분을 반환.
# span()
import re
txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.span())
(5, 7)
# string
import re
#The string property returns the search string:
txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.string)
The rain in Spain
# group()
import re
txt = "The rain in Spain"
x = re.search(r"ai", txt)
print(x.group())
ai
메타 문자란 원래 그 문자가 가진 의미가 아니라 특별한 의미로 사용되는 문자.
. ^ $ * + ? \ | ( ) { } [ ] 이 있음.
| Character | Description | Example |
|---|---|---|
| [] (Character Set) | 지정된 문자 집합중 하나와 일치 | "[a-m]" |
| \ (Backslash) | 특수 시퀀스를 나타내며 특수 문자를 이스케이프할 때 사용 | "\d" |
| . (Dot) | 개행 문자를 제외한 모든 단일 문자와 일치 | "he..o" |
| ^ (Caret) | 문자열의 시작과 일치 | "^hello" |
| $ (Dollar) | 문자열의 끝과 일치 | "planet$" |
| * (Asterisk) | 앞의 문자가 0회 이상 반복 | "he.*o" |
| + (Plus) | 앞의 문자가 1회 이상 반복 | "he.+o" |
| ? (Question Mark) | 앞의 문자가 0회 또는 1회 반복 | "he.?o" |
| {} (Braces) | 앞의 문자가 지정된 횟수만큼 정확히 반복 | "he.{2}o" |
| | (Pipe) | 둘 중 하나. 또는. | "falls\|stays" |
| () (Group) | 패턴내에서 하위 패턴을 그룹화 | "(text)" |
import re
pattern = r"a..d" # a로 시작하고 d로 끝나며 사이에 2개의 문자가 들어가는 경우
text = "abcd aabb ab d aabcddabcd"
matches = re.findall(pattern, text)
print(matches)
['abcd', 'ab d', 'abcd', 'abcd'] # 공백도 문자열. a로 시작하고 d로 끝나는 4개의 문자 찾기.
문자열이 시작과 일치하는지.
import re
pattern = r"^The"
text = "The cat sat on the mat."
match = re.search(pattern, text)
print(match)
<re.Match object; span=(0, 3), match='The'>
문자열이 끝과 일치하는지.
import re
pattern = r"mat\.$"
text = "The cat sat on the mat."
match = re.search(pattern, text)
print(match)
<re.Match object; span=(19, 23), match='mat.'>
앞의 문자가 0번이상 반복되는지.
import re
pattern = r"bo*" # b로 시작하고 *앞의 o가 0회이상 반복되는 패턴 찾기.
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['boooo']
import re
pattern = r"o*" # 0회 이상이기 때문에 o가 없어도됨. o가 연속해서 나타나는 부분 및 없는 부분 모두 찾음.
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['', '', '', '', 'o', '', '', '', '', 'oooo', '', '', '', '', '', '', '', '', '', '']
앞의 문자가 1회이상 반복되는 패턴찾기.
import re
pattern = r"bo+"
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['boooo']
import re
pattern = r"o+"
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['o', 'oooo']
import re
pattern = r"bo?"
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['bo']
import re
pattern = r"o?"
text = "A ghost booooed at me." # o를 'oooo'로 인식하지않고 o,o,o,o로 인식
matches = re.findall(pattern, text)
print(matches)
['', '', '', '', 'o', '', '', '', '', 'o', 'o', 'o', 'o', '', '', '', '', '', '', '', '', '', '']
앞의 문자가 지정된 횟수만큼 반복되는 패턴.
import re
pattern = r"a{3}"
text = "aaaaa aa a aaaa aaa a"
matches = re.findall(pattern, text)
print(matches)
['aaa', 'aaa', 'aaa']
# 궁금해서 해봄
import re
pattern = r"a{0}"
text = "aa"
matches = re.findall(pattern, text)
print(matches)
['', '', ''] # 왜일까?
import re
pattern = r"cat|dog"
text = "I have a cat and a hamster."
matches = re.findall(pattern, text)
print(matches)
['cat']
패턴 내에서 하위 패턴을 그룹화함.
import re
pattern = r"(oo)+"
text = "A ghost booooed at me."
matches = re.findall(pattern, text)
print(matches)
['oo'] # 왜 ['oooo']가 아니고 ['oo']일까? 위에서 "o+"할때는 ['oooo']였는데...
집합은 다음과 같은 특별한 의미를 가진 한 쌍의 대괄호 [] 안에 있는 문자 집합.
| Set | Description |
|---|---|
[arn] | 지정된 문자 중 하나(a, r, n)가 있는 경우 일치 |
[a-n] | a부터 n까지의 소문자 중 하나에 일치 |
[^arn] | a, r, n을 제외한 모든 문자에 일치 |
[0123] | 지정된 숫자 중 하나(0, 1, 2, 3)가 있는 경우 일치 |
[0-9] | 0부터 9까지의 숫자 중 하나에 일치 |
[0-5][0-9] | 00부터 59까지의 두 자리 숫자에 일치 |
[a-zA-Z] | a부터 z까지, 그리고 A부터 Z까지의 대소문자에 일치 |
[+] | 문자열에서 + 문자에 일치 (세트 내에서 +, *, ., |, (), $, {}는 특별한 의미를 가지지 않음) |
\기호 뒤에 문자는 특별한 의미를 가지고 있다.
| Character | Description | Example |
|---|---|---|
\A | 문자열의 시작 부분에 지정된 문자들이 있는 경우 일치 | "\\AThe" |
| \b (Word Boundary) | 지정된 문자들이 단어의 시작이나 끝에 있을 때 일치 (문자열 앞의 'r'은 문자열이 'raw string'으로 처리되는 것을 확실하게 함) | r"\\bain", r"ain\\b" |
\B | 지정된 문자들이 존재하지만 단어의 시작이나 끝이 아닐 때 일치 (문자열 앞의 'r'은 문자열이 'raw string'으로 처리되는 것을 확실하게 함) | r"\\Bain", r"ain\\B" |
| \d (Digit) | 문자열이 숫자(0-9)를 포함하는 경우 일치 | "\\d" |
| \D | 문자열이 숫자를 포함하지 않는 경우 일치 | "\\D" |
| \s (Space) | 문자열이 공백 문자를 포함하는 경우 일치 | "\\s" |
| \S | 문자열이 공백 문자를 포함하지 않는 경우 일치 | "\\S" |
| \w (Word Character) | 문자열이 단어 문자(a부터 Z, 0-9까지의 숫자, 밑줄(_) 문자)를 포함하는 경우 일치 | "\\w" |
| \W | 문자열이 단어 문자를 포함하지 않는 경우 일치 | "\\W" |
\Z | 지정된 문자들이 문자열의 끝에 있는 경우 일치 | "Spain\\Z" |
import re
pattern = r"\d+" # 숫자가 1회이상 반복
text = "There are 15 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
['15', '10']
import re
pattern = r"\d" # 숫자찾기
text = "There are 15 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
['1', '5', '1', '0']
import re
pattern = r"\D+" # 숫자가 아닌 문자가 1회이상 반복
text = "There are 15 apples and 10 oranges."
matches = re.findall(pattern, text)
print(matches)
['There are ', ' apples and ', ' oranges.']
HTML 태그 찾기 및 제거
import re
html = "<html><head><title>Title</title></head><body>Body content</body></html>"
# HTML 태그 찾기
tags = re.findall(r"<[^>]+>", html)
print(tags)
['<html>', '<head>', '<title>', '</title>', '</head>', '<body>', '</body>', '</html>']
패턴 : < [^>]+ >
< : 태그 시작
[^>]+ : >가 아닌 모든 문자가 1회이상 반복
> : 태그 종료
비밀번호 강도 검사
import re
password = "Password123"
# 최소 8자, 숫자와 대소문자 포함
pattern = r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"
패턴 : ^ (?=.*[a-z]) (?=.*[A-Z]) (?=.*\d) .{8,} $
^ : 문자열이 시작. [^abc]와 다름.
() : 그룹지정
? : 전방탐색.
= : 전방탐색을 사용하는데 특정 패턴이 있는지 확인하기위해.
. : 임의의 문자열.
* : 바로앞의 패턴이 0회이상 반복
[a-z] : 소문자 알파벳
[A-Z] : 대문자 알파벳
\d : 숫자.
. : 임의의 문자.
{8,} : 바로 앞의 패턴이 8회이상 반복. {8}: 8회, {8,10}: 8회이상 10회이하
$ : 문자열의 끝.
이메일 주소 추출
import re
text = "Contact us at support@example.com or sales@example.com."
emails = re.findall(r"[\w\.-]+@[\w\.-]+\.\w+", text)
print(emails)
['support@example.com', 'sales@example.com']
패턴 : [\w\.-]+ @ [\w\.-]+ \. \w+
[] : 대괄호는 문자 클래스를 나타냄.
\w : word character. 문자열이 단어 문자(a부터 Z, 0-9까지의 숫자, 밑줄(_) 문자)를 포함.
.- : 점(.), 하이픈(-) 그대로 해석.
+ : 앞에 있는 패턴(\w.-)이 1회이상 반복됨.
@ : 이메일 주소에서 로컬파트와 도메인파트를 구분.
. : 점(.). 도메인파트와 최상위 도메인을 구분하는 점.
로그 파일에서 특정 패턴 찾기
import re
log = "2024-04-01 12:00:00, ERROR, The application encountered an unexpected error"
# 날짜와 시간 패턴 찾기
pattern = r"\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}"
date_time = re.search(pattern, log)
if date_time:
print("날짜 및 시간:", date_time.group())
날짜 및 시간: 2024-04-01 12:00:00
패턴: \d{4} - \d{2} - \d{2} \d{2} : \d{2} : \d{2}
\d : 숫자.
{4} : 4회 반복.
추가로 공부할것
Flags
re.IGNORECASE, re.MULTILINE, re.DOTALL, re.ASCII, re.DEBUG