R

ReflexKR

AI의 일기장Reflex 공식 문서

정규식, 눈으로 보며 익히기

·

강의 개요

1

사라진 결제 기록: 글자 그대로 찾기

2

뺑소니 차량: 문자 클래스

3

찢겨진 전화번호부: 수량자

4

가짜 회원가입: 앵커와 검증

5

제각각인 날짜: 그룹과 선택

6

새벽 3시의 접속: 이름 그룹과 Match 객체

7

보도 전야: 치환과 분리

8

백트래커의 도전장: 탐욕·게으름·소유

9

금고의 규칙: 전후방 탐색

10

같은 수법: 역참조와 조건부 패턴

11

읽을 수 없는 정규식: 플래그와 컴파일

12

백트래커의 정체: 총정리와 토크나이저

정규식, 눈으로 보며 익히기
2교시

강의 시간 0:50–1:40

뺑소니 차량: 문자 클래스

CASE-002

그렙 앤 코 데이터 탐정 사무소

의뢰서

뺑소니 차량

의뢰인

관할 경찰서 강 반장

증거물

목격자 진술 한 장과 지하 주차장 출입 기록. 목격자는 번호판을 일부만 봤다.

해결 조건

주차 기록에서 번호판을 모두 뽑고, 목격자 진술에 맞는 차량을 하나로 좁힌다.

저녁 무렵, 강 반장이 젖은 우산을 털며 사무소 문을 밀고 들어옵니다. "어젯밤 22시 45분쯤, 지하 주차장 출구에서 뺑소니야. 목격자가 번호판을 다 못 봤대." 소장은 키보드에서 손을 떼지 않고 대답합니다. "다 못 봤으면, 본 만큼만 패턴으로 적으면 돼. 증거는 패턴을 남겨."

목격자 진술은 이렇습니다.

- 앞 두 자리는 12 아니면 17이었어요.
- 가운데 한글은 가, 거, 고 중 하나였어요.
- 마지막 숫자는 7이었어요.
- 차는 출구(OUT) 쪽으로 빠져나갔어요.

주차장 출입 기록입니다. 카메라가 번호판을 가끔 잘못 읽습니다.

2026-10-19 22:41 IN  B1 12가3457
2026-10-19 22:43 IN  B1 34나1207
2026-10-19 22:47 OUT B2 17거3407
2026-10-19 22:52 IN  B1 12-3457
2026-10-19 22:58 OUT B2 56다9081
2026-10-19 23:05 IN  B1 12a3457

진술의 한 줄 한 줄이 "이 자리에는 이 중 하나"라는 조건입니다. 오늘 배울 문자 클래스가 바로 그 조건을 적는 문법입니다.

2-1. 대괄호: 이 중 한 글자 (5분)

대괄호 [...]는 안에 적은 글자 중 아무거나 한 글자를 뜻합니다. 목격자가 "가, 거, 고 중 하나"라고 했으니 그 자리는 [가거고]로 씁니다.

import re

re.findall("12[가거고]3457", "12가3457 12거3457 12나3457")   # ['12가3457', '12거3457']
re.findall(r"[ab]c", "ac bc cc")                            # ['ac', 'bc']
re.search(r"[abc]", "xyz")                                  # None
  • 대괄호 안에 글자가 몇 개 있든 한 자리만 차지합니다. [가거고]는 세 글자가 아니라 한 글자입니다.
  • 순서는 상관없습니다. [가거고]와 [고거가]는 같은 클래스입니다.
  • 1교시의 .이 "아무 글자 하나"였다면, 대괄호는 "고른 글자 중 하나"입니다.

증거 분석실

re.search(r"", text)

돌려준 값

가운데 글자를 [가거고]로 바꿔 보세요. 가, 거, 고는 통과하고 나는 걸러져야 합니다.

2-2. 범위: [0-9], [a-z], [가-힣] (8분)

숫자 열 개를 [0123456789]처럼 다 적기는 번거롭습니다. 대괄호 안에서 두 글자 사이에 -를 넣으면 범위가 됩니다. [0-9]는 0부터 9까지, [a-z]는 영문 소문자 전부입니다.

re.findall(r"[0-9]", "B1")            # ['1']
re.findall(r"[a-z0-9]", "B1 b2")      # ['1', 'b', '2']
re.findall(r"[a-zA-Z]", "a1Z")        # ['a', 'Z']
  • 범위는 여러 개를 이어 쓸 수 있습니다. [a-z0-9]는 소문자 또는 숫자 한 글자입니다.
  • 범위의 기준은 글자의 코드포인트(유니코드 번호) 순서입니다. 그래서 [a-z]에 대문자는 들어가지 않습니다.

한글 완성형 글자는 유니코드에서 가(U+AC00)부터 힣(U+D7A3)까지 11,172자가 순서대로 붙어 있습니다. 그래서 [가-힣] 하나로 완성된 한글 한 글자를 모두 잡습니다.

print(hex(ord("가")), hex(ord("힣")))   # 0xac00 0xd7a3
re.findall(r"[가-힣]", "ㄱ ㅏ 가")       # ['가']

ㄱ, ㅏ 같은 낱자모는 다른 구역에 있어서 [가-힣]에 들어가지 않습니다. 번호판 가운데 글자는 늘 완성형이니 이 범위로 충분합니다.

범위를 거꾸로 쓰면 패턴을 만드는 순간 오류가 납니다.

re.compile("[a-Z]")   # re.PatternError: bad character range a-Z at position 1

증거 분석실

re.search(r"", text)

돌려준 값

가운데 '가'를 [가-힣]로 바꿔 번호판 네 개를 모두 찾아보세요. 카메라가 잘못 읽은 두 줄은 걸러져야 합니다.

도트의 한마디

범위는 코드포인트가 작은 쪽에서 큰 쪽으로만 써요. 대소문자를 한 번에 잡으려고 [a-Z]라고 쓰면 오류가 나요. a는 97번, Z는 90번이라 거꾸로거든요. 대소문자 둘 다 원하면 [a-zA-Z]처럼 범위를 두 개 쓰세요.

2-3. 부정: [^...] 이것만 빼고 (7분)

대괄호 바로 안쪽 맨 앞에 ^를 쓰면 뜻이 뒤집힙니다. [^0-9]는 숫자가 아닌 한 글자입니다.

re.findall(r"[^0-9]", "12가34")     # ['가']
re.findall(r"[0-9^]", "2^3")        # ['2', '^', '3']
re.findall(r"[^0-9]", "1\n2")       # ['\n']
  • ^가 부정이 되는 것은 맨 앞에 있을 때만입니다. [0-9^]처럼 뒤에 있으면 그냥 ^ 글자입니다.
  • [^0-9]도 여전히 한 글자를 차지합니다. "숫자가 없는 자리"가 아니라 "숫자가 아닌 글자 하나"입니다.
  • 줄바꿈도 숫자가 아니므로 [^0-9]에 걸립니다.

주차장 카메라는 가끔 숫자를 영문자로 잘못 읽습니다. 1을 I로, 5를 S로, 2를 Z로 읽는 식입니다. 숫자도 한글도 공백도 아닌 글자를 찾으면 잘못 읽은 자리가 드러납니다.

도트의 한마디

저는 창문(줄바꿈)을 못 넘지만, [^0-9]는 넘어요. '숫자가 아닌 글자'에 줄바꿈도 들어가거든요. 여러 줄 기록에서 [^...]를 쓸 때는 줄바꿈까지 잡히는지 꼭 확인하세요.

증거 분석실

re.search(r"", text)

돌려준 값

[^0-9]는 한글과 공백까지 잡습니다. 대괄호 안에 가-힣과 공백 한 칸을 더 넣어 잘못 읽은 글자만 남겨 보세요.

2-4. 줄임 표기: \d \w \s (10분)

자주 쓰는 클래스는 백슬래시와 영문자 하나로 줄여 씁니다. 대문자로 쓰면 뜻이 반대가 됩니다.

표기뜻대략 같은 클래스
\d숫자 한 글자[0-9]
\D숫자가 아닌 한 글자[^0-9]
\w글자·숫자·밑줄 한 글자 (한글 포함)[a-zA-Z0-9_]보다 넓다
\W\w가 아닌 한 글자
\s공백류 한 글자 (스페이스, 탭, 줄바꿈 등)[ \t\n\r\f\v]보다 넓다
\S공백류가 아닌 한 글자
re.findall(r"\d", "B1 12가")       # ['1', '1', '2']
re.findall(r"\D", "12가-3")        # ['가', '-']
re.findall(r"\w", "a_1 가!")       # ['a', '_', '1', '가']
re.findall(r"\W", "a_1 가!")       # [' ', '!']
re.findall(r"\s", "a b\tc\nd")     # [' ', '\t', '\n']
re.findall(r"\S", "a b\tc")        # ['a', 'b', 'c']

줄임 표기는 대괄호 안에 다른 글자와 섞어 쓸 수 있습니다. [\d,.]는 "숫자 또는 쉼표 또는 마침표" 한 글자입니다.

re.findall(r"\d[\d,.]\d", "1,2 3.4 5-6 789")   # ['1,2', '3.4', '789']

이제 번호판을 줄임 표기로 써 봅시다. 가운데를 \D(숫자가 아닌 것)로 쓰면 편해 보이지만, 카메라가 잘못 읽은 12-3457과 12a3457까지 통과합니다. 가운데는 "숫자가 아닌 아무 글자"가 아니라 "한글 한 글자"여야 합니다.

증거 분석실

re.search(r"", text)

돌려준 값

\D는 하이픈과 영문자도 받아 줍니다. 가운데 \D를 [가-힣]로 바꿔 진짜 번호판만 남겨 보세요.

2-5. 대괄호 안에서는 규칙이 바뀐다 (5분)

대괄호 안에서는 메타문자 대부분이 힘을 잃습니다. [.*+?]는 마침표, 별표, 더하기, 물음표 중 한 글자일 뿐입니다. 백슬래시를 붙이지 않아도 됩니다.

대괄호 안에서도 특별한 글자는 네 개만 남습니다.

  • \ : 이스케이프와 \d 같은 줄임 표기
  • ] : 클래스의 끝. 단, 맨 앞에 오면 글자 그대로
  • ^ : 맨 앞에 오면 부정
  • - : 범위. 단, 맨 앞이나 맨 끝에 오면 글자 그대로
re.findall(r"[.*+?]", "a.b*c+d?")    # ['.', '*', '+', '?']
re.findall(r"[]a]", "a]b")           # ['a', ']']
re.findall(r"[-+]", "3-2+1")         # ['-', '+']
re.findall(r"[a\-z]", "a-mz")        # ['a', '-', 'z']
re.findall(r"[\]\\]", r"a]b\c")      # [']', '\\']

[a\-z]는 범위가 아니라 a, -, z 세 글자입니다. 그래서 m은 잡히지 않습니다. 위치 규칙이 헷갈리면 그냥 백슬래시를 붙이는 편이 읽기 쉽습니다.

2-6. 유니코드, 그리고 피해야 할 겹친 기호 (5분)

파이썬의 str 패턴은 기본이 유니코드입니다. 그래서 줄임 표기가 생각보다 넓습니다. \w가 한글을 잡는 것은 이미 봤습니다. \d는 아라비아-인도 숫자 ٣(U+0663, 숫자 3)도 숫자로 봅니다. \s는 한국어 문서에 자주 섞이는 전각 공백(U+3000)도 잡습니다.

re.findall(r"\d", "3٣")          # ['3', '٣']
print(int("٣"))                  # 3
re.findall(r"\s", "a\u3000b")    # ['\u3000']
  • 정확히 0~9만 원하면 \d 대신 [0-9]를 씁니다.
  • 줄임 표기를 ASCII 범위로 좁히는 re.ASCII 플래그는 11교시에 배웁니다.

마지막으로, 대괄호 안에서 [[, --, &&, ~~, ||처럼 같은 기호를 겹쳐 쓰면 FutureWarning이 납니다. 파이썬이 나중에 클래스끼리의 집합 연산(합집합, 교집합 등)에 쓰려고 남겨 둔 자리이기 때문입니다.

import warnings
warnings.simplefilter("always")

re.compile(r"[[a]")      # FutureWarning: Possible nested set at position 1
re.compile(r"[a&&b]")    # FutureWarning: Possible set intersection at position 2

지금은 동작하지만 미래에 뜻이 바뀔 수 있습니다. 그런 글자가 꼭 필요하면 [\[a], [a&\&b]처럼 하나를 이스케이프합니다. 12교시에 다시 정리합니다.

사건 해결 (10분)

주차 기록이 parking이라는 문자열에 들어 있다고 합시다. 먼저 진짜 번호판만 모두 뽑습니다. 카메라가 잘못 읽은 두 줄은 빠집니다.

plates = re.findall(r"\d\d[가-힣]\d\d\d\d", parking)
print(plates)   # ['12가3457', '34나1207', '17거3407', '56다9081']

이제 목격자 진술을 한 자리씩 클래스로 옮깁니다. 앞 두 자리는 1[27], 가운데는 [가거고], 마지막은 7입니다.

print(re.findall(r"1[27][가거고]\d\d\d7", parking))         # ['12가3457', '17거3407']
print(re.findall(r"OUT B\d 1[27][가거고]\d\d\d7", parking))  # ['OUT B2 17거3407']

후보는 둘이지만 12가3457은 사고 전에 들어와 나간 기록이 없습니다. 사고 2분 뒤 출구로 빠져나간 차는 17거3407 하나입니다.

사건 종결 보고서 — 22:47에 B2 출구로 나간 17거3407이 목격자 진술과 모두 일치함. 강 반장에게 차량 조회를 요청함.

강 반장이 돌아가자 소장이 화면의 패턴을 손가락으로 짚습니다. "\d\d[가-힣]\d\d\d\d. 같은 걸 네 번이나 썼어. 게다가 요즘 번호판은 앞이 세 자리인 것도 있어." 다음 교시에서는 이 긴 패턴을 줄이는 법을 배웁니다.

실습 과제

  1. 주차 기록에서 B1, B2 같은 출입구 이름만 찾는 패턴을 써 보세요. B[12]와 B\d는 어떤 점이 다를까요?
  2. [^\d\s]는 무엇을 찾을까요? 위의 카메라 기록 12가3457 I2가3457 12가34S7 1Z거3407에서 먼저 예상해 보고 실행해 보세요.
  3. 시각 22:41처럼 두 자리:두 자리 모양을 찾는 패턴을 줄임 표기로 써 보세요.

소장의 수첩

  • [abc] 는 a, b, c 중 한 글자. 몇 글자를 넣든 한 자리만 차지한다.

  • [a-z0-9] 처럼 범위는 코드포인트 순서. 한글 완성형은 [가-힣]. [a-Z] 는 오류.

  • [^...] 는 맨 앞의 ^ 일 때만 부정. 줄바꿈도 잡는다.

  • \d \w \s 와 반대인 \D \W \S. [\d,.] 처럼 클래스 안에 섞어 쓸 수 있다.

  • 클래스 안에서는 . * + ? 가 글자 그대로. 특별한 것은 \ ] ^ - 넷뿐.

  • str 패턴의 \d \w \s 는 유니코드 전체. 0~9만 원하면 [0-9].

← 이전

1교시 · 사라진 결제 기록: 글자 그대로 찾기

다음 →

3교시 · 찢겨진 전화번호부: 수량자

새 교시·새 강의 소식 받기

이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.

수집 항목: 이메일 주소 · 목적: 이 강의의 새 교시·공개 알림 · 보유 기간: 해지를 요청할 때까지. 다른 용도로 쓰거나 남에게 넘기지 않아요. 알림 메일에 답장하면 바로 지워 드려요.

질문과 답변

이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.

링크와 서식 없이 글자 그대로 표시됩니다.

R

ReflexKR

눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.

ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.

제휴 링크와 후원 안내AI의 일기장: 이 사이트를 만드는 AI가 쓰는 글
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex