⚙️
중급 (Intermediate)
re · 메타문자 · 그룹 · sub · compile
9주차 — 정규표현식
re 모듈로 패턴 매칭, 자주 쓰는 메타문자(\d \w \s + * ?), 그룹 캡처(이름 그룹 포함), 치환(sub), raw string, compile 활용까지.
regexrepatternsub
소요 시간
⏱ 2시간
난이도
📊 중급
선수 조건
🎯 8주차
결과물
이메일·전화번호·로그에서 패턴 추출
이 강의에서 배우는 것
- 1re 모듈로 패턴 매칭한다
- 2자주 쓰는 메타문자(\d, \w, +, *, ?)를 안다
- 3그룹 캡처를 활용한다
- 4치환(re.sub)을 사용한다
1. 기본 사용법
python
import re
# 검색 (첫 매치)
m = re.search(r"\d{3}-\d{4}-\d{4}", "전화: 010-1234-5678")
print(m.group()) # 010-1234-5678
# 모두 찾기
print(re.findall(r"\d+", "사과 5개, 바나나 3개")) # ['5', '3']
# 매치 여부
if re.match(r"^\d+$", "12345"):
print("숫자만")
# 치환
print(re.sub(r"\d+", "?", "사과 5개, 바나나 3개")) # 사과 ?개, 바나나 ?개2. 자주 쓰는 메타문자
| 패턴 | 의미 |
|---|---|
| . | 임의 한 글자 |
| \d | 숫자 ([0-9]) |
| \D | 숫자 아님 |
| \w | 영숫자, 언더스코어 |
| \s | 공백 |
| ^ | 시작 |
| $ | 끝 |
| [abc] | a, b, c 중 하나 |
| [^abc] | a, b, c 가 아닌 것 |
| [a-z] | 범위 |
3. 수량자
| 패턴 | 의미 |
|---|---|
| * | 0회 이상 |
| + | 1회 이상 |
| ? | 0 또는 1회 |
| {n} | 정확히 n회 |
| {n,m} | n~m회 |
| ? (수량자 뒤) | 최소 매칭 (탐욕 X) |
4. 그룹 캡처
(...) 로 그룹화. m.group(N) 또는 m.groups() 로 추출.
python
log = "ERROR 2026-05-09 14:32 인증 실패"
m = re.match(r"(\w+) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)", log)
print(m.groups()) # ('ERROR', '2026-05-09', '14:32', '인증 실패')
# 명명 그룹
m = re.match(r"(?P<level>\w+) (?P<date>\d{4}-\d{2}-\d{2})", log)
print(m.group("level"), m.group("date"))5. raw string
이스케이프 충돌 방지. 정규식은 항상 r"..." 권장.
python
re.findall(r"\d+", text) # 권장
re.findall("\\d+", text) # 동일하지만 헷갈림6. 컴파일
같은 패턴을 여러 번 쓰면 미리 컴파일.
python
email_re = re.compile(r"[\w.]+@[\w.]+\.\w+")
emails = email_re.findall("연락: a@b.com, c@d.co.kr")
print(emails)7. 자주 하는 실수
- 이스케이프 누락 — r 안 붙여서 \d → d. 항상 r"..."
- 탐욕 매칭 — <.+> 가 <a><b> 전체를 매칭. 최소 매칭은 <.+?>
- match vs search — match 는 처음만, search 는 어디서든
- 너무 복잡한 정규식 — 가독성 떨어지면 그냥 코드로 분기
8. FAQ
Q1. 정규식 디버깅 어떻게?
https://regex101.com 같은 사이트에서 패턴 테스트.
Q2. 정규식이 느린가요?
일반적으로 빠름. 다만 catastrophic backtracking 패턴은 매우 느림.
Q3. 이메일 완벽 검증 패턴?
RFC 정확 검증은 매우 길다. 실용적 수준이면 [\w.+-]+@[\w-]+\.[\w.-]+.
💻 예제 (examples)
실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.
01_basic.py— search / findall / match / sub
CODE
import re
m = re.search(r"\d{3}-\d{4}-\d{4}", "전화: 010-1234-5678")
print(m.group())
print(re.findall(r"\d+", "사과 5개, 바나나 3개"))
if re.match(r"^\d+$", "12345"):
print("숫자만")
print(re.sub(r"\d+", "?", "사과 5개, 바나나 3개"))
▶ 실행 결과
010-1234-5678
['5', '3']
숫자만
사과 ?개, 바나나 ?개02_meta.py— 메타문자 + 수량자
CODE
import re
text = "Hello 123 abc456 XYZ"
print(re.findall(r"\d+", text)) # 숫자
print(re.findall(r"[a-z]+", text)) # 소문자
print(re.findall(r"[A-Z]+", text)) # 대문자
print(re.findall(r"\w+", text)) # 단어
print(re.findall(r"\b\w{3}\b", text)) # 정확히 3글자
▶ 실행 결과
['123', '456']
['ello', 'abc']
['H', 'XYZ']
['Hello', '123', 'abc456', 'XYZ']
['XYZ']03_groups.py— 그룹 + 명명 그룹
CODE
import re
log = "ERROR 2026-05-09 14:32 인증 실패"
m = re.match(r"(\w+) (\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}) (.+)", log)
print(m.groups())
m2 = re.match(r"(?P<level>\w+) (?P<date>\d{4}-\d{2}-\d{2})", log)
print(m2.group("level"), m2.group("date"))
▶ 실행 결과
('ERROR', '2026-05-09', '14:32', '인증 실패')
ERROR 2026-05-0904_sub_compile.py— sub + compile
CODE
import re
# 전화번호 마스킹
phones = "연락: 010-1234-5678, 02-345-6789"
masked = re.sub(r"(\d{2,3})-\d{3,4}-\d{4}", r"\1-****-****", phones)
print(masked)
# compile + 재사용
email_re = re.compile(r"[\w.]+@[\w.]+\.\w+")
print(email_re.findall("연락: a@b.com, c@d.co.kr, 잘못된 이메일"))
▶ 실행 결과
연락: 010-****-****, 02-****-****
['a@b.com', 'c@d.co.kr']📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
과제 1
이메일 추출
목표: 긴 텍스트에서 이메일 주소만 모두 추출.
요구사항
- re.findall + 적당한 패턴
입출력 예시
['hong@example.com', 'kim.young@a.co.kr', 'support+xyz@my-site.io']▶정답 코드 펼치기 / 접기
SOLUTION
import re
text = """문의는 hong@example.com 또는 kim.young@a.co.kr 로 보내주세요.
긴급은 support+xyz@my-site.io"""
emails = re.findall(r"[\w.+-]+@[\w-]+\.[\w.-]+", text)
print(emails)
▶ 실행 결과
['hong@example.com', 'kim.young@a.co.kr', 'support+xyz@my-site.io']과제 2
휴대폰 번호 검증
목표: 010-XXXX-XXXX 형식이 맞는지 True/False.
요구사항
- re.fullmatch 사용 권장
입출력 예시
010-1234-5678: True
02-1234-5678: False
01012345678: False▶정답 코드 펼치기 / 접기
SOLUTION
import re
def is_phone(s):
return bool(re.fullmatch(r"010-\d{4}-\d{4}", s))
for v in ["010-1234-5678", "02-1234-5678", "01012345678"]:
print(f"{v}: {is_phone(v)}")
▶ 실행 결과
010-1234-5678: True
02-1234-5678: False
01012345678: False과제 3
로그에서 ERROR만 추출
목표: 여러 줄 로그 중 ERROR 라인의 시각·메시지만.
요구사항
- 각 줄 형식: LEVEL YYYY-MM-DD HH:MM 메시지
- LEVEL == ERROR 인 것만
입출력 예시
[14:32] 인증 실패
[14:35] DB 연결 끊김▶정답 코드 펼치기 / 접기
SOLUTION
import re
log = """INFO 2026-05-09 14:30 서버 시작
ERROR 2026-05-09 14:32 인증 실패
WARN 2026-05-09 14:34 느린 응답
ERROR 2026-05-09 14:35 DB 연결 끊김"""
for line in log.splitlines():
m = re.match(r"ERROR \d{4}-\d{2}-\d{2} (\d{2}:\d{2}) (.+)", line)
if m:
print(f"[{m.group(1)}] {m.group(2)}")
▶ 실행 결과
[14:32] 인증 실패
[14:35] DB 연결 끊김