← 파이썬 강의 목록으로
⚙️
중급 (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. 자주 하는 실수

  1. 이스케이프 누락 — r 안 붙여서 \d → d. 항상 r"..."
  2. 탐욕 매칭 — <.+> 가 <a><b> 전체를 매칭. 최소 매칭은 <.+?>
  3. match vs search — match 는 처음만, search 는 어디서든
  4. 너무 복잡한 정규식 — 가독성 떨어지면 그냥 코드로 분기

8. FAQ

Q1. 정규식 디버깅 어떻게?

https://regex101.com 같은 사이트에서 패턴 테스트.

Q2. 정규식이 느린가요?

일반적으로 빠름. 다만 catastrophic backtracking 패턴은 매우 느림.

Q3. 이메일 완벽 검증 패턴?

RFC 정확 검증은 매우 길다. 실용적 수준이면 [\w.+-]+@[\w-]+\.[\w.-]+.

💻 예제 (examples)

실제로 실행해 결과를 확인할 수 있는 예제 코드입니다.

01_basic.pysearch / 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-09
04_sub_compile.pysub + 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 연결 끊김
예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗