회원가입 폼에서 아이디는 영문 소문자와 숫자만 허용하고 싶다고 해봐요. 또는 텍스트 뭉치에서 한글 이름만 쏙 뽑아내고 싶을 때도 있고요. 이런 걸 하려면 정규식에서 문자 하나를 표현하는 방법부터 알아야 해요. 오늘은 정규식의 가장 기본 재료인 메타문자문자 클래스를 다뤄볼게요. 이 두 가지만 손에 익어도 웬만한 패턴은 직접 만들어 쓸 수 있어요.


02.1 메타문자가 뭔가요?

정규식에서 대부분의 문자는 그냥 그 문자 자체를 뜻해요. 예를 들어 정규식 cat은 문자열 안에서 c, a, t가 순서대로 붙어있는 부분을 찾아요. 근데 몇몇 문자는 특별한 의미를 가지고 있어서, 문자 하나 대신 이런 종류의 문자라는 규칙을 나타내요. 이런 문자들을 메타문자(meta character)라고 불러요.


가장 많이 쓰는 메타문자 세 가지부터 소개할게요. 첫번째는 점(.)이에요. 줄바꿈 문자를 제외하고 아무 문자 한 개나 다 매칭돼요. c.t라는 정규식을 쓰면 cat도 잡고 cut도 잡고 c5t도 다 잡아요, 가운데 뭐가 오든 상관없다는 뜻이거든요.


두번째는 \d예요. digit(숫자)의 앞글자를 따온 건데, 0부터 9까지 숫자 한 글자를 뜻해요. 세번째는 \w인데, word(단어)의 앞글자로 알파벳이나 숫자나 밑줄(_) 중 아무거나 한 글자를 뜻해요. 영어 알파벳뿐 아니라 한글도 포함이에요. 마지막으로 \s는 공백 문자 한 개, 그러니까 스페이스나 탭이나 줄바꿈 같은 걸 뜻해요.


바로 실습해볼게요.

import re
re.findall(r"\d+", "사과 3개, 배 12개")
결과: ['3', '12']


정규식 \d+에서 +는 바로 앞에 있는 걸 한 번 이상 반복하라는 뜻이에요. 지금은 \d 하나가 숫자 한 글자를 뜻한다는 것만 기억해두시면 돼요.


02.2 문자 하나를 콕 집어 고르고 싶을 때는요?

\d\w는 편하지만 범위가 너무 넓을 때가 있어요. 예를 들어 a, b, c 중에 하나만 매칭하고 싶다면 대괄호를 써서 [abc]라고 쓰면 돼요. 이걸 문자 클래스(character class)라고 불러요. 대괄호 안에 있는 문자 중 아무거나 딱 한 글자만 매칭한다는 뜻이에요.


범위를 지정할 수도 있어요. 하이픈(-)을 이용해서 [a-z]라고 쓰면 소문자 a부터 z까지 아무거나 한 글자, [0-9]라고 쓰면 숫자 0부터 9까지 아무거나 한 글자를 뜻해요. 둘을 합쳐서 [a-z0-9]라고 쓰면 소문자 알파벳이거나 숫자인 문자 한 개를 매칭해요.


사실 \d[0-9]랑, \w[a-zA-Z0-9_]랑 거의 같은 뜻이에요. 자주 쓰니까 미리 축약형으로 만들어둔 셈이죠.


아이디 검사 예시를 볼게요, 영문 소문자와 숫자만 허용한다고 해봐요.

re.fullmatch(r"[a-z0-9]+", "user01")
결과: 매칭 성공
re.fullmatch(r"[a-z0-9]+", "User01")
결과: None (대문자 U 때문에 실패)


fullmatch는 문자열 전체가 패턴과 정확히 일치해야 성공으로 쳐주는 함수예요. 대문자 U가 [a-z0-9] 범위 밖이라서 실패한 거예요, 대소문자를 둘 다 허용하고 싶으면 [a-zA-Z0-9]처럼 범위를 늘려주면 돼요.


대괄호는 알파벳이나 숫자 말고 다른 글자들도 똑같이 담을 수 있어요. 예를 들어 이름 칸에 영문이든 한글이든 둘 다 받고 싶으면 [a-zA-Z가-힣]+처럼 두 범위를 나란히 붙여 쓰면 돼요. 대괄호 안에 여러 범위를 이어 쓰면 그 범위들을 전부 합친 것으로 동작해요, 순서는 상관없어요.


02.3 반대로 이 문자는 아니어야 할 때는요?

가끔은 이 문자들만 빼고 나머지 아무거나를 표현하고 싶을 때가 있어요. 이럴 땐 대괄호 맨 앞에 캐럿(^)을 붙이면 돼요. [^0-9]라고 쓰면 숫자가 아닌 문자 아무거나 한 글자를 뜻해요.


앞서 나온 \d, \w, \s도 각각 반대 버전이 있어요. 소문자를 대문자로만 바꾸면 뜻이 정반대가 돼요. \D는 숫자가 아닌 문자, \W는 알파벳도 숫자도 밑줄도 아닌 문자, \S는 공백이 아닌 문자를 뜻해요. 대소문자만 다른데 뜻은 완전히 반대라서 처음엔 헷갈리기 쉬워요, 이거 자주 뒤바꿔 쓰는 실수 정말 많이 해요.


import re
re.findall(r"\D+", "가격은 15000원입니다")
결과: ['가격은 ', '원입니다']


숫자가 아닌 부분만 쭉 뽑아냈더니 가격은 이랑 원입니다만 남았죠. 반대로 숫자만 뽑고 싶으면 \d+를 쓰면 돼요.


\S도 자주 쓰는데, 공백으로 나뉜 단어들을 쪼갤 때 유용해요. 여러 칸 띄어쓰기나 탭이 섞여 있어도 \S+는 공백이 아닌 덩어리만 잡아내니까, 문서마다 띄어쓰기 방식이 제각각이어도 안정적으로 단어를 뽑아낼 수 있어요.


02.4 대괄호 안에서는 뭘 조심해야 하나요?

대괄호 문자 클래스 안에서는 재밌는 규칙이 하나 있어요. 바깥에서는 메타문자였던 점(.), 별표(*), 더하기(+) 같은 기호들이 대괄호 안에서는 대부분 그냥 평범한 문자로 취급돼요. [.+*]라고 쓰면 이건 점 또는 더하기 또는 별표 중 한 글자라는 뜻이지, 특별한 규칙이 아니에요.


이 성질을 이용하면 점(.)을 진짜 마침표로 매칭하고 싶을 때도 편해요. 원래 점은 아무 문자나 매칭하는 메타문자니까, 진짜 마침표만 찾고 싶다면 \.처럼 백슬래시로 이스케이프하거나, 대괄호 안에 넣어서 [.]라고 써도 똑같은 효과가 나요.


하이픈(-)은 조금 더 조심해야 해요. [a-z]처럼 두 문자 사이에 있으면 범위를 뜻하지만, 대괄호 맨 앞이나 맨 뒤에 두면 그냥 리터럴 하이픈으로 취급돼요. 그래서 숫자나 하이픈을 매칭하고 싶으면 [0-9-]처럼 뒤에 붙이거나 [-0-9]처럼 앞에 붙이면 돼요. 중간에 어중간하게 넣으면 의도치 않은 범위로 해석될 수 있어서, 늘 맨 앞이나 맨 뒤에 두는 습관을 들이시길 추천해요.


전화번호에서 숫자와 하이픈만 남기고 싶다면 이렇게 쓸 수 있어요.

re.findall(r"[0-9-]+", "연락처 010-1234-5678 입니다")
결과: ['010-1234-5678']


02.5 한글이나 다른 언어는 어떻게 다루나요?

지금까지 예시가 대부분 영어 알파벳이었는데, 한글도 똑같은 방식으로 범위를 지정할 수 있어요. [가-힣]이라고 쓰면 완성형 한글 음절 하나를 뜻해요. 가부터 힣까지가 유니코드에서 한글 완성형 글자들이 모여있는 구간이라서, 이 범위 안에 있으면 한글 한 글자로 판단하는 거예요.


이름만 뽑아내는 예시를 볼게요.

re.findall(r"[가-힣]+", "user123 홍길동 admin 김철수")
결과: ['홍길동', '김철수']


여기서 조심할 점이 하나 있어요. 파이썬3의 \d는 아라비아 숫자 0부터 9만 매칭할 거라고 생각하기 쉬운데, 사실 유니코드 모드가 기본이라서 다른 언어권의 숫자 문자까지 매칭이 될 수 있어요. 딱 0부터 9까지 아라비아 숫자만 확실히 잡고 싶다면 \d 대신 [0-9]를 쓰는 게 더 안전해요. 저도 이거 모르고 있다가 이상한 데이터가 섞여 들어와서 한참 헤맸던 적이 있어요.


오늘 나온 예시들을 regex101에 직접 붙여넣고 대괄호 안팎을 바꿔가면서 결과가 어떻게 달라지는지 눈으로 확인해보세요. 메타문자랑 문자 클래스가 손에 익으면, 나머지는 이 부품들을 어떻게 조합하느냐의 문제일 뿐이에요.