파이썬 re 정규표현식 완벽 가이드: 패턴 매칭과 텍스트 정제
파이썬 re 모듈의 정규표현식 문법부터 match·search·fullmatch 차이, 그룹 추출, finditer, sub 데이터 정제와 성능 주의점까지 예제로 정리합니다.

파이썬에서 정규표현식은 re 모듈로 사용합니다. 핵심은 패턴을 raw string인 r"..."으로 작성하고, 문자열 전체 검증은 fullmatch(), 일부 검색은 search(), 여러 결과 추출은 finditer(), 치환·정제는 sub()로 목적에 맞게 구분하는 것입니다.
핵심 요약
- 정규표현식 패턴은 백슬래시 충돌을 줄이도록 raw string으로 작성합니다.
match,search,fullmatch는 검사 범위가 서로 다릅니다.- 데이터 추출은
findall·finditer, 정제는sub가 실용적입니다. - 복잡한 패턴은 이름 있는 그룹과
VERBOSE플래그로 읽기 쉽게 관리합니다.
정규표현식 기본 문법
정규표현식은 찾으려는 문자열의 규칙을 기호로 표현합니다. 일반 문자는 자기 자신과 일치하고, 마침표·대괄호·수량자 같은 메타 문자는 특별한 의미를 가집니다.
| 패턴 | 의미 | 예시 |
|---|---|---|
. |
줄바꿈을 제외한 임의 문자 1개 | a.c |
\d |
유니코드 숫자 문자 | \d{4} |
\w |
유니코드 단어 문자 | \w+ |
\s |
공백 문자 | \s+ |
^, $ |
문자열 또는 줄의 시작·끝 | ^Python$ |
*, +, ? |
0회 이상, 1회 이상, 0~1회 | ab+ |
{m,n} |
m회부터 n회까지 반복 | \d{2,4} |
[abc] |
문자 집합 중 하나 | [가-힣] |
[^abc] |
문자 집합을 제외한 하나 | [^0-9] |
A|B |
A 또는 B | cat|dog |
파이썬 문자열과 정규표현식 모두 백슬래시를 특별하게 해석합니다. 따라서 패턴은 일반적으로 r"\d+"처럼 raw string으로 작성하는 것이 안전하고 읽기도 쉽습니다.
import re
text = "주문번호 A-2026-0815"
pattern = r"[A-Z]-\d{4}-\d{4}"
result = re.search(pattern, text)
if result:
print(result.group())
match·search·fullmatch 차이
match()는 문자열 시작 위치에서만 패턴을 검사하고, search()는 전체 문자열을 훑어 첫 일치 위치를 찾습니다. fullmatch()는 문자열 전체가 패턴과 일치해야 성공합니다. 이메일·상품코드 같은 입력값 전체 검증에는 fullmatch()가 의도를 가장 분명하게 표현합니다.
import re
text = "ID: AB-1234"
print(re.match(r"AB-\d{4}", text)) # None
print(re.search(r"AB-\d{4}", text)) # Match
print(re.fullmatch(r"AB-\d{4}", text)) # None
print(re.fullmatch(r"AB-\d{4}", "AB-1234")) # Match
모든 결과 문자열만 필요하면 findall()이 간단합니다. 일치 위치, 그룹, 앞뒤 문맥도 필요하다면 Match 객체를 순회하는 finditer()가 적합합니다.
text = "상품 A-1200, 상품 B-3400"
pattern = re.compile(r"(?P<code>[A-Z])-(?P<price>\d+)")
for match in pattern.finditer(text):
print(match.group("code"), match.group("price"), match.span())
캡처 그룹과 이름 있는 그룹
괄호 (...)는 일치 문자열 중 필요한 부분을 따로 추출하는 캡처 그룹입니다. 순번 그룹은 패턴을 수정할 때 번호가 바뀌기 쉬우므로 실무에서는 (?P<name>...) 형식의 이름 있는 그룹이 유지보수에 유리합니다.
log = "2026-08-31 ERROR payment_failed"
pattern = re.compile(
r"(?P<date>\d{4}-\d{2}-\d{2})\s+"
r"(?P<level>INFO|WARNING|ERROR)\s+"
r"(?P<message>.+)"
)
match = pattern.fullmatch(log)
if match:
print(match.groupdict())
그룹이 필요하지 않고 문법적 묶음만 만들려면 (?:...) 비캡처 그룹을 사용합니다. 선택 항목과 반복 범위를 묶되 결과에 포함하지 않아 코드 의도를 명확히 할 수 있습니다.
sub와 split로 텍스트 데이터 정제
re.sub()은 패턴과 일치하는 부분을 다른 문자열로 바꿉니다. 중복 공백 제거, 전화번호 마스킹, HTML에서 가져온 텍스트의 불필요한 구분자 정리에 유용합니다. 원본 데이터를 바로 덮어쓰기 전에는 변환 결과를 표본으로 검증해야 합니다.
import re
raw = " 고객명: 홍길동 전화: 010-1234-5678 "
clean = re.sub(r"\s+", " ", raw).strip()
masked = re.sub(r"(010)-\d{4}-(\d{4})", r"\1-****-\2", clean)
print(masked)
여러 종류의 공백이나 쉼표·세미콜론을 모두 구분자로 쓰려면 re.split()이 편리합니다. 빈 문자열이 생길 수 있으므로 결과를 필터링하세요.
text = "사과, 배; 포도 수박"
items = [x for x in re.split(r"[,;\s]+", text) if x]
print(items)
대량 CSV나 엑셀의 문자열 열을 정제할 때는 패턴을 작은 표본에서 먼저 검증한 뒤 데이터프레임에 적용하세요. 결측값이 섞여 있다면 문자열 변환 순서도 확인해야 합니다.
플래그와 re.compile 활용
대소문자를 무시하려면 re.IGNORECASE, 여러 줄에서 ^와 $를 줄 단위로 적용하려면 re.MULTILINE, 마침표가 줄바꿈까지 포함하게 하려면 re.DOTALL을 사용합니다. 긴 패턴은 re.VERBOSE로 줄을 나누고 주석을 붙일 수 있습니다.
phone_pattern = re.compile(r"""
(?P<mobile>01[016789]) # 이동통신 식별번호
[-\s]?
(?P<middle>\d{3,4})
[-\s]?
(?P<last>\d{4})
""", re.VERBOSE)
print(phone_pattern.fullmatch("010-1234-5678"))
같은 패턴을 여러 번 사용한다면 re.compile()로 이름을 부여해 재사용하는 편이 좋습니다. Python은 최근 사용 패턴을 내부적으로 캐시하지만, 컴파일된 패턴을 변수에 두면 목적과 적용 위치가 더 명확해집니다.
실전 패턴 예제
숫자만 남기기
digits = re.sub(r"\D+", "", "주문 2026-0815")
연속 공백 하나로 줄이기
normalized = re.sub(r"\s+", " ", text).strip()
날짜 형식 추출
dates = re.findall(r"\b\d{4}-\d{2}-\d{2}\b", text)
한글 음절 구간 찾기
korean_words = re.findall(r"[가-힣]+", text)
사용자 입력을 문자 그대로 검색하기
keyword = input("검색어: ")
safe_pattern = re.compile(re.escape(keyword))
이메일이나 URL 형식은 예외가 많으므로 지나치게 거대한 정규표현식 하나로 모든 표준을 구현하기보다 서비스가 실제로 허용하는 범위를 정의하고 추가 검증 절차를 두는 것이 안전합니다.
정규표현식 성능과 보안 주의점
중첩된 반복과 모호한 대안이 많은 패턴은 특정 입력에서 과도한 백트래킹을 일으킬 수 있습니다. 외부 사용자가 긴 문자열을 제출할 수 있는 서버라면 입력 길이를 제한하고, 패턴을 단순화하며, 정규표현식만으로 모든 파서를 만들지 마세요. HTML·JSON·CSV처럼 구조가 있는 형식은 전용 파서가 더 안전합니다.
탐욕적 수량자 *와 +는 가능한 많이 일치합니다. 필요한 최소 구간만 찾으려면 *?, +? 같은 비탐욕적 수량자를 검토하되, 앞뒤 경계를 더 구체적으로 지정하는 것이 결과를 예측하기 쉽습니다.
실수 방지 체크리스트
- 패턴 문자열 앞에
r을 붙였는지 확인합니다. - 전체 검증에는
fullmatch()를 사용합니다. - 매치가 없을 때
None인지 확인한 뒤group()을 호출합니다. - 그룹이 많다면 이름 있는 그룹을 사용합니다.
- 사용자 입력은
re.escape()로 이스케이프할지 판단합니다. findall()에서 캡처 그룹 유무에 따라 반환 형식이 달라지는 점을 확인합니다.- 치환 전후 행 수와 표본 데이터를 비교합니다.
- 복잡한 구조 데이터에는 전용 파서를 사용합니다.
- 긴 외부 입력에 대한 성능을 테스트합니다.
자주 묻는 질문
1. re.match와 re.search의 차이는 무엇인가요?
match는 문자열 시작에서만 검사하고 search는 전체에서 첫 일치 위치를 찾습니다.
2. 입력값 전체를 검사하려면 무엇을 쓰나요?
re.fullmatch()를 사용하면 전체 문자열이 패턴과 일치할 때만 성공합니다.
3. 패턴 앞에 r을 붙이는 이유는 무엇인가요?
파이썬 문자열의 백슬래시 해석을 피하여 정규표현식 이스케이프를 읽기 쉽게 작성하기 위해서입니다.
4. findall과 finditer 중 무엇이 좋나요?
일치 문자열 목록만 필요하면 findall, 위치와 그룹 정보가 필요하면 finditer가 적합합니다.
5. 한글은 \w로 찾을 수 있나요?
Unicode 문자열의 기본 모드에서는 한글도 단어 문자로 처리될 수 있지만 원하는 범위가 한글 음절뿐이면 [가-힣]처럼 명시하세요.
6. 정규표현식으로 HTML을 파싱해도 되나요?
단순 정제에는 쓸 수 있지만 중첩 구조를 해석하려면 BeautifulSoup 같은 HTML 파서를 사용하는 것이 안전합니다.
7. 괄호를 쓰면 findall 결과가 왜 달라지나요?
캡처 그룹이 있으면 findall이 전체 매치 대신 그룹 문자열이나 튜플을 반환할 수 있습니다.
8. 대소문자를 무시하려면 어떻게 하나요?
re.IGNORECASE 또는 re.I 플래그를 사용합니다.
9. 사용자 검색어를 패턴에 넣어도 되나요?
사용자 입력을 정규식 문법이 아닌 문자 그대로 찾으려면 re.escape()를 적용하세요.
10. 정규표현식이 느려지는 이유는 무엇인가요?
모호한 대안과 중첩 반복이 결합되면 과도한 백트래킹이 발생할 수 있으므로 패턴과 입력 길이를 제한해야 합니다.
공식 출처와 확인일
- Python 공식 문서: re — Regular expression operations — 확인일 2026년 8월 31일
- Python 공식 HOWTO: Regular Expression — 확인일 2026년 8월 31일