정규표현식(Regular Expression)

앞서 파이썬의 고급 자료구조와 검색·정렬 알고리즘을 살펴보았습니다. 이번 글에서는 텍스트 데이터의 유효성 검사, 패턴 검색, 데이터 추출 및 변환을 수행하는 강력한 도구인 정규표현식(Regular Expression)의 모든 것을 완벽하게 파헤쳐 보겠습니다. 파이썬에서는 re 모듈을 통해 정규표현식을 자유롭게 사용할 수 있습니다.

1. 정규표현식의 기본 요소

정규표현식은 특정한 규칙을 가진 문자열의 집합을 표현하기 위해 사용되는 언어입니다.

1.1 문자 클래스 (Character Class)

문자 클래스는 대괄호([])로 묶어 사용하며, 여러 문자 중 하나와 일치하도록 합니다.

  • [abc]: a, b, c 중 하나와 일치

  • [0-9]: 숫자 (0부터 9까지)와 일치

  • [a-z]: 소문자 알파벳 (a부터 z까지)와 일치

  • [A-Z]: 대문자 알파벳 (A부터 Z까지)와 일치

  • [^abc]: a, b, c를 제외한 문자와 일치

  • 예제: [aeiou] (모든 모음 문자와 일치), [0-9a-fA-F] (16진수 문자와 일치)

1.2 메타문자 및 앵커 (Anchor)

정규표현식에서 특수한 의미를 가지는 문자들입니다.

  • .: 줄바꿈을 제외한 모든 문자와 일치

  • ^: 문자열의 시작을 나타냄

  • $: 문자열의 끝을 나타냄

  • \: 메타문자를 일반 문자로 인식하거나 특수 문자로 사용

  • |: OR 연산자(Alternation)

  • (): 캡처와 그룹

  • \b: 단어 경계와 일치, \B: 단어 경계가 아닌 곳과 일치

  • 예제: ^Hello (“Hello”로 시작), world$ (“world”로 끝남), \bword (단어 경계에서 시작)

1.3 반복 패턴 및 이스케이프 문자

  • *: 0번 이상 반복, +: 1번 이상 반복, ?: 0번 또는 1번 반복

  • {n}: 정확히 n번 반복, {n,}: 최소 n번 이상 반복, {n,m}: 최소 n번에서 최대 m번 반복

  • 이스케이프: 메타문자 앞에 백슬래시(\)를 붙여 일반 문자로 인식시킵니다. (예: \.은 실제 마침표를 의미)

1.4 특수 문자 (Special Characters)

  • \d: 숫자와 일치 (0-9), \D: 숫자가 아닌 것과 일치

  • \w: 알파벳, 숫자, 밑줄 문자와 일치 (a-z, A-Z, 0-9, _), \W: 그 외의 문자

  • \s: 공백 문자와 일치 (스페이스, 탭, 줄바꿈 등), \S: 공백이 아닌 것과 일치

2. 파이썬 re 모듈의 주요 함수

Python에서는 내장 모듈 re를 통해 정규표현식을 지원합니다.

함수 기능 반환 값 사용 목적
re.findall() 패턴과 일치하는 모든 부분을 찾습니다. 일치하는 문자열(또는 캡처 그룹)들의 리스트 텍스트에서 특정 패턴의 모든 발생을 추출할 때
re.search() 문자열 전체를 검색하여 가장 처음 일치하는 부분을 찾습니다. 일치 정보를 담은 Match object (없으면 None) 문자열 내에 특정 패턴이 존재하는지 확인할 때
re.split() 패턴과 일치하는 부분을 구분자로 사용하여 문자열을 나눕니다. 분할된 문자열들을 포함하는 리스트 특정 패턴을 기준으로 텍스트를 분리할 때
re.sub() 패턴과 일치하는 모든 부분을 새로운 문자열로 대체(치환)합니다. 치환된 내용이 적용된 새로운 문자열 텍스트에서 특정 패턴을 일괄 변경하거나 삭제할 때

주요 함수 활용 예시

import re

text = "사과 10개와 배 5개가 있습니다."
# 1. re.findall()
print(re.findall(r'\d+', text))  # 출력: ['10', '5']

# 2. re.search() 및 Match 객체 메서드
match = re.search(r'\d{4}년', "2024년 1월 1일")
if match:
    print(match.group(0))  # 일치한 문자열: 2024년
    print(match.span())    # 일치 위치 튜플: (0, 5)
    print(match.string)    # 전달된 전체 문자열

# 3. re.split()
print(re.split(r'[,;]', "Apple,Banana;Cherry"))  # 출력: ['Apple', 'Banana', 'Cherry']

# 4. re.sub()
print(re.sub(r'\d{3}-\d{4}-\d{4}', '***', "내 번호는 010-1234-5678"))
# 출력: 내 번호는 ***

3. 캡처 그룹(Capturing)과 비캡처 그룹(Non-Capturing)

그룹화(())를 사용할 때 그 결과를 메모리에 저장할지 여부를 결정하는 중요한 개념입니다.

  • 캡처 그룹 ((...)): 패턴을 하나로 묶어 연산을 적용함과 동시에, 일치한 문자열을 메모리에 저장하여 나중에 참조하거나 추출할 수 있게 합니다. 파이썬 re.findall()은 캡처 그룹이 있으면 전체 일치 항목 대신 캡처 그룹 안의 내용만 튜플 형태로 반환합니다.

  • 비캡처 그룹 ((?:...)): 그룹화 기능은 동일하게 수행하지만, 일치한 내용을 메모리에 저장하지 않습니다. 단순히 OR 연산이나 반복 범위를 지정할 때 사용하며, 성능과 코드 명확성 측면에서 권장됩니다.

import re
text = "apple, banana, cherry"
# 비캡처 그룹을 사용하여 반복 적용하되 캡처하지 않음
pattern = r'(?:apple|banana|cherry)' 
print(re.findall(pattern, text)) 
# 출력: ['apple', 'banana', 'cherry']

4. 정규표현식 사용 예

4.1 이메일 주소 찾기

[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}
  • [A-Za-z0-9._%+-]+: 사용자 이름 부분 (1회 이상 반복)

  • @[A-Za-z0-9.-]+: 도메인 이름 부분

  • \.[A-Za-z]{2,}: 최상위 도메인 (TLD, 최소 2자 이상)

import re

email_pattern = r'[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
emails = ["example@example.com", "invalid-email@domain", "user.name+tag@domain.co"]

for email in emails:
    if re.fullmatch(email_pattern, email):
        print(f"'{email}'은(는) 유효한 이메일입니다.")
    else:
        print(f"'{email}'은(는) 유효하지 않은 이메일입니다.")

4.2 전화번호 찾기

한국의 휴대전화 및 일반 전화 형식을 지원하는 정규표현식입니다.

(01[016789]-?\d{3,4}-?\d{4})|(0\d{1,2}-?\d{3,4}-?\d{4})
  • 01[016789]: 휴대전화 국번 (010, 011 등)

  • -?: 하이픈 유무 선택

  • \d{3,4} / \d{4}: 중간 및 마지막 자리 숫자

import re

phone_pattern = r'(01[016789]-?\d{3,4}-?\d{4})|(0\d{1,2}-?\d{3,4}-?\d{4})'
print(bool(re.fullmatch(phone_pattern, "010-1234-5678")))  # 출력: True

4.3 우편번호 찾기

대한민국의 5자리 우편번호 형식(12345)을 검증합니다.

^\d{5}$
import re
zipcode_pattern = r'^\d{5}$'
print(bool(re.fullmatch(zipcode_pattern, "12345")))  # 출력: True

4.4 URL 찾기

프로토콜, 도메인, 포트 및 경로/쿼리 문자열을 포함한 URL을 포괄적으로 찾습니다.

(?:https?|ftp)://(?:[^/\s]+)(?::\d+)?(?:/[^\s]*)?
import re

url_pattern = r'(?:https?|ftp)://(?:[^/\s]+)(?::\d+)?(?:/[^\s]*)?'
text = "웹사이트 방문: https://www.google.com/search?q=regex 및 ftp://data.org/file.zip"
print(re.findall(url_pattern, text))
# 출력: ['https://www.google.com/search?q=regex', 'ftp://data.org/file.zip']

4.5 날짜 찾기 및 유효성 검증

다양한 날짜 형식(YYYY-MM-DD, YYYY/MM/DD, DD-MM-YYYY 등)을 찾고, 파이썬의 datetime 모듈을 통해 실제 유효한 날짜인지 검증할 수 있습니다.

from datetime import datetime
import re

date_pattern = r'\b(?:\d{4}[-/]\d{2}[-/]\d{2}|\d{2}[-/]\d{2}[-/]\d{4})\b'

def is_valid_date(date_str):
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%d-%m-%Y", "%d/%m/%Y"):
        try:
            datetime.strptime(date_str, fmt)
            return True
        except ValueError:
            continue
    return False

text = "날짜 예시: 2023-10-29, 25/12/2022 그리고 잘못된 날짜 2022-13-40"
found_dates = re.findall(date_pattern, text)
valid_dates = [d for d in found_dates if is_valid_date(d)]

print("유효한 날짜들:", valid_dates)  # 출력: ['2023-10-29', '25/12/2022']

4.6 민감 정보 패턴 검색 (주민등록번호 등)

주민등록번호와 같이 ######-####### 형태를 이루는 고유 패턴을 안전하게 탐색하고 추출할 수 있습니다.

import re

jumin_pattern = r'\b\d{6}-\d{7}\b'
text = "식별 번호: 900101-1234567 및 850505-2345678"

extracted = re.findall(jumin_pattern, text)
print("추출된 식별 번호 개수:", len(extracted))

답글 남기기