Python 강좌 : 제 44강 - 정규 표현식
정규표현식(Regular Expression)
정규 표현식(Regular Expression)은 프로그래밍에서 사용하는 형식 언어입니다.
특정한 규칙을 가진 문자열을 검색, 분리, 치환하는 데 주로 활용되며, 특정한 패턴과 일치하는 텍스트를 입력값에서 찾아 반환합니다.
정규 표현식의 기본 문법은 대부분의 프로그래밍 언어에서 비슷한 의미를 갖습니다. 다만, 지원하는 기능이나 세부 문법은 언어(정규 표현식 엔진)마다 조금씩 다를 수 있습니다.
만약 정규 표현식을 사용하지 않고 문자열에서 특정 패턴을 찾는 경우 매우 복잡한 코드를 작성해야 합니다.
하지만 정규 표현식을 활용할 경우 코드가 매우 간결해지며 유사한 문자까지 일치시켜 검색할 수 있습니다.
제 10강의 문자열 메서드 find(), replace(), split()은 정확히 같은 문자열만 찾을 수 있습니다. “-” 를 찾거나 “사과“를 바꾸는 일에는 충분하지만, “숫자 4개 - 숫자 2개 형태의 날짜”, “@가 들어간 이메일 주소”, “괄호 안의 글자”처럼 모양(규칙) 으로 정의되는 문자열은 찾을 수 없습니다.
정규 표현식은 이런 규칙을 \d{4}-\d{2}처럼 짧은 패턴 문자열로 표현하고, 정규 표현식 엔진이 입력 문자열을 앞에서부터 읽으며 패턴과 일치하는 부분을 찾아 줍니다.
이번 강좌에서는 패턴을 구성하는 문자와 플래그, 패턴을 컴파일하고 검색하는 re 모듈의 함수, 검색 결과를 담는 매치(Match) 객체, 그리고 전방·후방 탐색, 원자 그룹 같은 고급 기능을 차례로 다룹니다.
- Tip : 정확한 문자열을 찾거나 바꾸는 작업이라면 문자열 메서드가 더 빠르고 읽기 쉽습니다. 정규 표현식은 규칙으로 정의되는 문자열을 다룰 때 사용합니다.
패턴 정의
정규 표현식을 사용하는 과정은 패턴 정의 → 컴파일 → 검색(또는 분리, 치환) 의 세 단계입니다.
먼저 전체 예제를 실행해 본 다음, 각 단계를 하나씩 나눠서 살펴보겠습니다. 예제는 문장에서 소괄호 안에 있는 글자를 찾습니다.
import re
string = """
정규 표현식(Regular Expression)은 프로그래밍에서 사용하는 형식 언어입니다.
특정한 규칙을 가진 문자열을 검색, 분리, 치환하는 데 주로 활용되며, 특정한 패턴과 일치하는 텍스트를 입력값에서 찾아 반환합니다.
정규 표현식을 사용하지 않고 문자열에서 특정 패턴을 찾는 경우 매우 복잡한 코드를 작성해야 합니다.
하지만 정규 표현식을 활용할 경우 코드가 매우 간결해지며 유사한 문자까지 일치시켜 검색할 수 있습니다.
"""
pattern = re.compile(r"\((.*?)\)")
find = re.findall(pattern, string)
print(find)- 결과
- [‘Regular Expression’]
import rePython에서 정규 표현식 모듈은 re로, 정규 표현식을 사용하기 위해 import합니다.
import re
pattern = re.compile(r"\((.*?)\)")
print(pattern)- 결과
- re.compile(‘\\((.*?)\\)’)
정규 표현식을 사용하기 위해, 찾을 문자열의 패턴을 정의합니다.
정규 표현식의 패턴을 구성할 때 일반적으로 패턴의 문자열은 로 문자열 표기법(Raw string notation)으로 작성합니다.
로 문자열 표기법은 문자열 앞에 r을 붙여 r"패턴"의 형태로 작성하며, 역슬래시(\)를 이스케이프 문자로 해석하지 않고 그대로 정규 표현식 엔진에 전달합니다.
결과에서 역슬래시가 두 번씩 표시되는 이유는, 일반 문자열로 표현할 때 역슬래시 하나를 \\로 표기하기 때문입니다.
위 예제에서 패턴은 \((.*?)\)를 의미합니다.
이 패턴을 정규 표현식 컴파일 함수(re.compile)로 Python 환경에 맞게 컴파일합니다.
re.compile(패턴, 플래그)을 의미합니다.
패턴은 검출할 문자열의 정규 표현식입니다.
플래그는 컴파일할 때 추가적인 설정을 의미합니다.
컴파일이란 사람이 읽는 패턴 문자열을 분석해, 엔진이 빠르게 실행할 수 있는 내부 형태로 바꾸는 과정입니다. 패턴에 문법 오류가 있다면 이 단계에서 예외가 발생합니다.
import re
p = re.compile(r"(\d+)-(\d+)", re.IGNORECASE)
print(p.pattern, p.flags, p.groups)
print(p.search("번호 12-345 입니다").group())
print(p.flags == re.IGNORECASE | re.UNICODE)- 결과
- (\d+)-(\d+) 34 2
12-345
True
re.compile()이 반환하는 패턴(Pattern) 객체는 원래의 패턴 문자열(pattern), 적용된 플래그(flags), 그룹의 개수(groups)를 속성으로 가지고 있습니다.
flags가 지정한 re.IGNORECASE(2)가 아니라 34인 이유는, 문자열 패턴에는 re.UNICODE(32)가 기본으로 함께 적용되기 때문입니다. 마지막 줄에서 두 플래그를 합친 값과 같음을 확인할 수 있습니다.
패턴 객체는 p.search(), p.findall()처럼 re 모듈의 함수와 같은 이름의 메서드를 가지며, 이때는 패턴 인수를 생략합니다.
- Tip :
re.findall(r"...", 문자열)처럼 문자열 패턴을 직접 전달해도re모듈이 내부적으로 컴파일하고 최근에 사용한 패턴을 캐시(cache) 해 둡니다. 그러므로 성능 차이는 크지 않으며, 같은 패턴을 여러 곳에서 사용할 때 이름을 붙여 재사용하려는 목적으로re.compile()을 사용하는 경우가 많습니다.
먼저, 패턴에 들어가는 표현식과 플래그는 다음의 표와 같습니다.
메타 문자
메타 문자(Meta Character)는 글자 그대로의 의미가 아니라 특별한 규칙을 나타내는 문자입니다. 예를 들어 .은 마침표가 아니라 “아무 문자 하나”를, *는 별표가 아니라 “앞의 것이 0회 이상 반복”을 의미합니다.
| 메타 문자 | 설명 |
|---|---|
| . | 줄바꿈 문자를 제외한 모든 문자 하나와 매치 |
| ? | 앞의 문자(패턴)가 0회 또는 1회 반복 |
| * | 앞의 문자(패턴)가 0회 이상 반복 |
| + | 앞의 문자(패턴)가 1회 이상 반복 |
| ^ | 문자열의 시작과 일치(MULTILINE 플래그 설정 시 각 행의 처음과 매치) |
| $ | 문자열의 끝과 일치(MULTILINE 플래그 설정 시 각 행의 마지막과 매치) |
| | | 문자 OR 연산(둘 중 하나라도 매치) |
| [] | 문자의 집합(집합 중 하나라도 매치) |
| [^] | 문자 NOT 연산([^abc] = a, b, c 문자를 제외) |
| [-] | 문자 범위 집합([0-9] = 0~9까지의 숫자만 매치) |
| {n} | 앞의 문자(패턴)가 n회 반복 |
| {n,m} | 앞의 문자(패턴)가 n회 이상, m회 이하 반복 |
| {n,} | 앞의 문자(패턴)가 n회 이상 반복 |
| () | 정규식 그룹화(캡처 그룹) |
| (?:) | 그룹화하되 캡처하지 않음(비캡처 그룹) |
| (?P<name>) | 이름이 있는 그룹 |
?, *, +, {n,m}처럼 반복 횟수를 지정하는 문자를 수량자(Quantifier)라 합니다. 수량자는 바로 앞의 문자 하나에 적용되므로, 여러 글자를 반복하려면 (ab)+처럼 소괄호로 묶습니다.
[] 안에서는 대부분의 메타 문자가 의미를 잃습니다. 예를 들어 [.*]는 마침표 또는 별표 문자 하나와 매치됩니다.
이스케이프 문자 목록
이스케이프 문자는 역슬래시와 문자를 조합해 자주 쓰는 문자의 종류나 위치를 표현합니다. \d는 “숫자 하나”, \b는 “단어의 경계(글자가 아닌 위치)”를 의미합니다.
| 이스케이프 문자 | 설명 |
|---|---|
| \w | 유니코드 문자, 숫자, 밑줄과 매치(한글 포함, ASCII 플래그 사용 시 [a-zA-Z0-9_]) |
| \W | \w를 제외한 매치 |
| \d | 유니코드 숫자와 매치(ASCII 플래그 사용 시 [0-9]) |
| \D | \d를 제외한 매치 |
| \s | 공백 문자와 매치(ASCII 기준 [ \t\n\r\f\v]) |
| \S | 공백 문자를 제외한 매치 |
| \b | 단어의 경계(시작 또는 끝)와 매치 |
| \B | 단어의 경계가 아닌 위치와 매치 |
| \A | 문자열의 처음과 매치 |
| \Z | 문자열의 마지막과 매치 |
| \z | 문자열의 마지막과 매치(\Z와 동일, Python 3.14 이상) |
| \\ | 역슬래시 |
| \t | 탭 |
| \n | 줄바꿈 |
| ( | 좌측 소괄호 |
| ) | 우측 소괄호 |
- Tip :
.,*,?,(처럼 정규 표현식에서 특별한 의미를 갖는 문자를 문자 그대로 찾으려면 앞에 역슬래시를 붙입니다.re.escape(문자열)을 사용하면 특수 문자를 자동으로 이스케이프합니다.
import re
print(re.escape("1+1=2?"))
print(re.findall("a.b", "a.b axb"))
print(re.findall(re.escape("a.b"), "a.b axb"))- 결과
- 1\+1=2\?
[‘a.b’, ‘axb’]
[‘a.b’]
re.escape()는 +, ?처럼 의미가 있는 문자 앞에 역슬래시를 붙인 문자열을 반환합니다.
두 번째 줄처럼 "a.b"를 그대로 패턴으로 사용하면 .이 “아무 문자”로 해석되어 axb까지 매치됩니다. 사용자가 입력한 검색어처럼 글자 그대로 찾아야 하는 문자열을 패턴에 넣을 때는 re.escape()로 감싸야 세 번째 줄처럼 의도한 결과를 얻을 수 있습니다.
최소 매칭 문자 목록
| 최소 매칭 문자 | 설명 |
|---|---|
| ?? | ?의 기능에서 반환되는 문자열을 최소 크기로 매치 |
| *? | *의 기능에서 반환되는 문자열을 최소 크기로 매치 |
| +? | +의 기능에서 반환되는 문자열을 최소 크기로 매치 |
| {n,m}? | {n,m}의 기능에서 반환되는 문자열을 최소 크기로 매치 |
소유 수량자 목록
| 소유 수량자 | 설명 |
|---|---|
| ?+ | ?와 같지만 매치한 문자열을 되돌려주지 않음 |
| *+ | *와 같지만 매치한 문자열을 되돌려주지 않음 |
| ++ | +와 같지만 매치한 문자열을 되돌려주지 않음 |
| {n,m}+ | {n,m}과 같지만 매치한 문자열을 되돌려주지 않음 |
| (?>) | 원자 그룹(그룹 안에서 매치한 문자열을 되돌려주지 않음) |
소유 수량자(Possessive Quantifier)와 원자 그룹(Atomic Group)은 Python 3.11 이상에서 사용할 수 있습니다.
탐색 문자 목록
| 탐색 문자 | 설명 |
|---|---|
| X(?=Y) | 긍정형 전방 탐색(X 뒤에 Y가 와야 X와 매치) |
| X(?!Y) | 부정형 전방 탐색(X 뒤에 Y가 오지 않아야 X와 매치) |
| (?<=Y)X | 긍정형 후방 탐색(X 앞에 Y가 있어야 X와 매치) |
| (?<!Y)X | 부정형 후방 탐색(X 앞에 Y가 없어야 X와 매치) |
탐색 문자 안의 Y는 조건을 검사할 뿐, 매치 결과에는 포함되지 않습니다.
플래그 목록
| 플래그 | 설명 |
|---|---|
| re.A re.ASCII |
\w, \W, \b, \B, \d, \D, \s, \S를 아스키코드로 매칭 |
| re.U re.UNICODE |
\w, \W, \b, \B, \d, \D, \s, \S를 유니코드로 매칭(문자열 패턴의 기본값이므로 생략 가능) |
| re.L re.LOCALE |
\w, \W, \b, \B를 현재 로케일 설정으로 매칭(바이트 패턴에서만 사용 가능) |
| re.I re.IGNORECASE |
대소문자 구분 없이 매칭 |
| re.M re.MULTILINE |
문자열이 여러 줄인 경우 메타 문자 ^와 메타 문자 $는 각 행의 처음과 끝에 매칭 |
| re.S re.DOTALL |
메타 문자 .이 줄바꿈 문자도 포함해서 매칭 |
| re.X re.VERBOSE |
정규 표현식에 주석을 사용할 수 있도록 변경(#과 공백은 무시됨. 공백을 활용할 경우 메타 문자 \를 사용) |
여러 플래그를 함께 사용할 때는 re.I | re.M처럼 | 연산자로 결합합니다.
정규 표현식에서 사용되는 문자와 플래그는 위와 같습니다.
모두 외울 필요는 없으며, 필요한 기능을 찾아서 응용해 활용하시면 됩니다.
메타 문자, 이스케이프 문자, 최소 매칭 문자는 정규 표현식의 패턴을 정의할 때 사용됩니다.
예제에서 사용된 패턴 문자는 다음과 같습니다.
\((.*?)\)\(와 \)는 이스케이프 문자로, 좌측 소괄호와 우측 소괄호를 의미합니다.
즉, 소괄호로 감싸진 (.*?)를 찾는다는 의미가 됩니다.
다음으로 나타나는 소괄호는 메타 문자로 정규식 그룹화를 의미합니다.
정규식 그룹화란 여러 패턴 문자 중, 실제로 찾고 싶은 문자를 하나의 그룹으로 만드는 역할을 합니다.
그러므로, 소괄호 사이에 포함된 문자(.*?)만 찾고 싶다는 의미가 됩니다.
- Tip :
re.findall()은 패턴에 그룹이 있으면 전체 매치가 아닌 그룹에 해당하는 문자열만 반환합니다. 그러므로 예제의 결과에는 소괄호가 포함되지 않습니다.
.은 메타 문자이며, *?은 최소 매칭 문자입니다.
.은 줄바꿈 문자를 제외한 모든 문자 하나와 매치됩니다. 즉, 줄바꿈을 제외한 모든 문자를 찾습니다.
다음으로, *?은 *의 기능에서 반환되는 문자열을 최소 크기로 매치입니다.
먼저, *는 앞의 문자가 0회 이상 반복된다는 뜻입니다.
이를 다시 정리해서 풀이한다면 다음과 같습니다.
줄 바꿈 문자를 제외한 모든 문자를 0개 이상 찾되, 최소 크기로 찾는다.
*, +, ?, {n,m}은 기본적으로 가능한 한 많이 매치하는 탐욕적(Greedy) 방식이며, 뒤에 ?를 붙이면 가능한 한 적게 매치하는 게으른(Lazy) 방식이 됩니다.
여기서 최소 크기는 다음과 같이 확인할 수 있습니다.
import re
text = "<b>굵게</b> 그리고 <i>기울임</i>"
print(re.findall(r"<.*?>", text))
print(re.findall(r"<.*>", text))- 결과
- [‘<b>’, ‘</b>’, ‘<i>’, ‘</i>’]
[‘<b>굵게</b> 그리고 <i>기울임</i>’]
<.*?>는 < 다음에 처음으로 나타나는 >에서 매치를 끝내므로, 각 태그를 하나씩 찾습니다.
<.*>는 가능한 한 많이 매치한 뒤 마지막 >에서 매치를 끝내므로, 문자열 전체가 하나로 매치됩니다.
엔진의 동작을 따라가 보면, 탐욕적인 .*는 먼저 줄 끝까지 모든 문자를 삼킨 다음 뒤의 >를 맞추기 위해 한 글자씩 되돌아가며(역추적) 처음 만나는 >, 즉 마지막 >에서 멈춥니다.
게으른 .*?는 반대로 0글자부터 시작해 한 글자씩 늘려 가며 매번 뒤의 >가 맞는지 확인하므로, 처음 만나는 >에서 멈춥니다.
다시, \((.*?)\)를 풀이한다면 다음과 같습니다.
소괄호로 둘러 싸인 문자열을 찾아 하나의 그룹으로 형성한다.
예제의 문자열은 한 줄에 소괄호가 한 쌍만 있으므로, 예제 기준으로는 ?를 포함 유/무는 큰 차이가 없습니다.
하지만, 소괄호 안에 또 다른 소괄호가 있다면 결과가 달라집니다.
import re
s = "(Regular (정규 표현식) Expression)"
print(re.findall(r"\((.*?)\)", s))
print(re.findall(r"\((.*)\)", s))
print(re.findall(r"\(([^()]*)\)", s))- 결과
- [‘Regular (정규 표현식’]
[‘Regular (정규 표현식) Expression’]
[‘정규 표현식’]
위와 같은 문자열에서 \((.*?)\)와 \((.*)\)를 적용한 결과는 다음과 같습니다.
\((.*?)\) : Regular (정규 표현식
\((.*)\) : Regular (정규 표현식) Expression
게으른 방식은 첫 번째 (에서 시작해 처음 만나는 )에서 끝나므로, 안쪽 괄호의 여는 괄호까지 포함된 어중간한 결과가 나옵니다. 탐욕적 방식은 마지막 )까지 매치하므로 바깥 괄호 전체의 내용을 얻습니다.
세 번째 패턴 \(([^()]*)\)는 괄호 안에 다른 괄호가 없는 부분만 찾도록 문자 집합으로 제한했으므로, 가장 안쪽 괄호의 내용 정규 표현식만 찾습니다.
그러므로, 정규 표현식의 패턴을 정의할 때 전체 데이터의 구조가 어떻게 되어있는지 고려해서 패턴을 구성해야 합니다.
위와 같은 경우가 빈번하게 발생하며 원하지 않는 문자열 패턴이 검출되거나 검출하지 못할수도 있습니다.
- Tip : 정규 표현식은 괄호의 짝(중첩 깊이) 을 셀 수 없습니다. 임의로 중첩된 괄호, HTML, JSON처럼 구조가 있는 데이터는 전용 파서(parser)를 사용하는 것이 안전합니다.
import re
pattern = re.compile(r"\((.*?)\)")
find = re.findall(pattern, "정규 표현식(Regular Expression)")
print(find)
print(pattern.findall("정규 표현식(Regular Expression)"))- 결과
- [‘Regular Expression’]
[‘Regular Expression’]
패턴 정의가 모두 완료됐다면, 목록 반환 검출 함수(re.findall)로 문자열에서 패턴을 검출할 수 있습니다.
re.findall(패턴, 문자열, 플래그)를 의미하며, 목록(List)의 형태로 검출 결과를 반환합니다.
컴파일된 패턴 객체는 pattern.findall(문자열)처럼 메서드로도 같은 기능을 사용할 수 있습니다. 같은 패턴을 여러 번 사용할 때는 미리 컴파일해 두는 것이 좋습니다.
import re
text = "2026-10-11, 2025-01-02"
print(re.findall(r"\d{4}-\d{2}-\d{2}", text))
print(re.findall(r"(\d{4})-\d{2}-\d{2}", text))
print(re.findall(r"(\d{4})-(\d{2})-(\d{2})", text))
print(re.findall(r"(?:\d{4})-\d{2}-\d{2}", text))- 결과
- [‘2026-10-11’, ‘2025-01-02’]
[‘2026’, ‘2025’]
[(‘2026’, ‘10’, ‘11’), (‘2025’, ‘01’, ‘02’)]
[‘2026-10-11’, ‘2025-01-02’]
re.findall()의 반환 형태는 패턴에 있는 캡처 그룹의 개수에 따라 달라집니다.
| 캡처 그룹 개수 | 반환 형태 |
|---|---|
| 0개 | 매치된 전체 문자열의 리스트 |
| 1개 | 그룹에 해당하는 문자열의 리스트 |
| 2개 이상 | 그룹들의 튜플로 이루어진 리스트 |
첫 줄은 그룹이 없으므로 날짜 전체를, 두 번째 줄은 연도만 그룹으로 묶었으므로 연도만 반환합니다. 세 번째 줄은 그룹이 3개이므로 (연, 월, 일) 튜플의 리스트가 됩니다.
네 번째 줄의 (?:...)는 묶기만 하고 캡처하지 않는 비캡처 그룹이므로, 그룹이 없는 것과 같이 전체 문자열을 반환합니다. 수량자를 적용하려고 괄호로 묶었을 뿐인데 findall()의 결과가 바뀌는 것을 막을 때 사용합니다.
로 문자열(Raw string)을 사용해야 하는 이유
정규 표현식 패턴은 Python 문자열로 작성되므로, 엔진에 전달되기 전에 Python의 문자열 이스케이프 처리를 먼저 거칩니다.
Python 문자열과 정규 표현식이 모두 역슬래시를 특별한 문자로 사용하기 때문에, 두 단계의 해석이 겹치면 의도하지 않은 패턴이 만들어질 수 있습니다.
import re
print(re.findall("\bcat\b", "cat concat"))
print(re.findall(r"\bcat\b", "cat concat"))
print(len("\b"), len(r"\b"))- 결과
- []
[‘cat’]
1 2
일반 문자열에서 \b는 Python이 먼저 백스페이스 문자 하나로 해석합니다. 그러므로 정규 표현식 엔진에는 단어 경계(\b)가 아닌 백스페이스 문자가 전달되어 아무것도 찾지 못합니다.
로 문자열 r"\b"는 역슬래시와 b 두 글자가 그대로 전달되므로, 의도한 대로 단어 경계로 동작합니다.
두 번째 줄에서 concat 안의 cat이 검출되지 않은 이유는, con 다음 위치가 글자와 글자 사이라서 단어의 경계가 아니기 때문입니다.
세 번째 줄의 1 2는 일반 문자열 "\b"가 글자 1개(백스페이스), 로 문자열 r"\b"가 글자 2개(역슬래시와 b)라는 것을 보여 줍니다.
- Tip : 정규 표현식 패턴은 항상
r"..."형태의 로 문자열로 작성하는 것을 권장합니다. Python 3.12 이상에서는 일반 문자열에"\d"처럼 유효하지 않은 이스케이프 시퀀스를 쓰면SyntaxWarning이 발생합니다.
매치 객체
re.findall()은 찾은 문자열만 돌려주므로 간단하지만, 어디에서 찾았는지, 그룹별로 어떻게 나뉘는지는 알 수 없습니다.
이런 정보가 필요할 때는 검색 결과를 매치(Match) 객체로 반환하는 re.search(), re.match(), re.fullmatch(), re.finditer()를 사용합니다.
import re
string = """
정규 표현식(Regular Expression)은 프로그래밍에서 사용하는 형식 언어입니다.
특정한 규칙을 가진 문자열을 검색, 분리, 치환하는 데 주로 활용되며, 특정한 패턴과 일치하는 텍스트를 입력값에서 찾아 반환합니다.
정규 표현식을 사용하지 않고 문자열에서 특정 패턴을 찾는 경우 매우 복잡한 코드를 작성해야 합니다.
하지만 정규 표현식을 활용할 경우 코드가 매우 간결해지며 유사한 문자까지 일치시켜 검색할 수 있습니다.
"""
pattern = re.compile(r"\((.*?)\)")
match = re.search(pattern, string)
print(match)
print(match.group(), match.start(0))- 결과
- <re.Match object; span=(7, 27), match=’(Regular Expression)’>
(Regular Expression) 7
앞선 목록 반환 검출 함수(re.findall)로 문자열을 검출한다면 단순하게 찾은 문자열만 반환합니다.
만약, 더 세부적인 정보를 필요로 한다면 매치(Match) 객체를 반환해야합니다.
매치 객체는 검출된 문자열의 그룹을 상세하게 나누거나 검출된 문자열의 색인 값 등 자세한 정보를 담고 있습니다.
그룹은 패턴에 포함된 소괄호()를 의미합니다.
매치 반환 전체 문자열 검출 함수(re.search)로 문자열에서 패턴을 검출할 수 있습니다.
re.search(패턴, 문자열, 플래그)를 의미하며, 매치(Match)의 형태로 검출 결과를 반환합니다.
결과의 span=(7, 27)은 매치된 부분이 문자열의 7번 색인부터 27번 색인 직전까지라는 뜻입니다. 문자열이 줄바꿈 문자(\n)로 시작하므로, 정규 표현식의 6글자와 합쳐 (의 위치가 7이 됩니다.
match.group()은 그룹 번호를 생략하면 0번 그룹, 즉 소괄호를 포함한 매치 전체를 반환합니다. findall()과 달리 전체 매치와 그룹을 모두 따로 꺼낼 수 있습니다.
- Tip : 검출되지 않으면
None을 반환하므로, 매치 객체를 사용하기 전에if match:처럼 확인하는 것이 안전합니다.
매치 객체의 메서드와 속성은 다음과 같습니다.
매치 메서드
| 메서드 | 설명 |
|---|---|
| match.group() | 매칭된 전체 문자열(0번째 그룹)을 반환 |
| match.group(n/name) | 매칭된 문자열의 n번째 또는 이름이 name인 그룹을 반환 |
| match.groups() | 매칭된 문자열의 모든 그룹을 튜플로 반환 |
| match.groupdict() | 매칭된 문자열의 이름 있는 그룹을 사전으로 반환 |
| match.start(n/name) | 매칭된 문자열 그룹의 시작 색인 값을 반환 |
| match.end(n/name) | 매칭된 문자열 그룹의 종료 색인 값을 반환 |
| match.span(n/name) | 매칭된 문자열 그룹의 (시작, 끝)에 해당되는 색인 값을 튜플로 반환 |
매치 속성
| 속성 | 설명 |
|---|---|
| match.string | 입력 문자열(string)을 반환 |
| match.pos | 입력 문자열(string)의 검색을 시작하는 위치 반환 |
| match.endpos | 입력 문자열(string)의 검색을 종료하는 위치 반환 |
| match.lastindex | 마지막으로 매칭된 그룹의 번호를 반환(없을 경우 None을 반환) |
| match.lastgroup | 마지막으로 매칭된 그룹의 이름을 반환(없을 경우 None을 반환) |
import re
m = re.search(r"(?P<year>\d{4})-(?P<month>\d{2})", "날짜: 2026-10")
print(m.group(), m.group(1), m.group("month"))
print(m.groups(), m.groupdict())
print(m.start(), m.end(), m.span(), m.span("month"))
print(m.lastindex, m.lastgroup)- 결과
- 2026-10 2026 10
(‘2026’, ‘10’) {‘year’: ‘2026’, ‘month’: ‘10’}
4 11 (4, 11) (9, 11)
2 month
(?P<이름>패턴)의 형태로 그룹에 이름을 지정하면, 번호뿐만 아니라 이름으로도 그룹에 접근할 수 있습니다.
첫 줄은 전체 매치 2026-10, 1번 그룹 2026, 이름이 month인 그룹 10입니다. 이름 있는 그룹도 왼쪽부터 번호가 매겨지므로 year는 1번, month는 2번입니다.
세 번째 줄의 4 11은 날짜: 의 4글자 다음부터 매치가 시작해 11번 색인 직전에서 끝났다는 뜻이며, 월 그룹은 (9, 11) 구간입니다. 끝 색인은 슬라이싱처럼 포함되지 않는 위치이므로 string[4:11]이 매치된 문자열입니다.
마지막 줄은 마지막으로 매치된 그룹이 2번이며 이름이 month라는 뜻입니다.
이름 있는 그룹은 패턴이 길어져도 m.group(3)처럼 번호를 세지 않아도 되므로, 그룹이 여러 개일 때 코드를 읽기 쉽게 만들어 줍니다.
import re
m = re.search(r"(\d+)원|(\d+)달러", "가격 30달러")
print(m.groups(), m.group(1), m.group(2), m.lastindex)- 결과
- (None, ‘30’) None 30 2
|로 나뉜 패턴에서는 매치에 참여하지 않은 그룹이 생길 수 있으며, 그 그룹의 값은 None입니다.
30달러는 오른쪽 대안과 매치되었으므로 1번 그룹은 None, 2번 그룹은 30입니다. group(1)의 결과를 바로 문자열처럼 사용하면 오류가 발생할 수 있으므로, |와 그룹을 함께 쓸 때는 None인지 확인합니다.
import re
it = re.finditer(r"\d+", "a1 b22 c333")
print(type(it).__name__)
for m in it:
print(m.group(), m.span())- 결과
- callable_iterator
1 (1, 2)
22 (4, 6)
333 (8, 11)
매치 반환 전체 문자열 검출 함수(re.search)는 하나의 대상만 검출합니다.
만약, 예제에서 쉼표(,) 앞에 있는 문자열을 검출한다고 가정하면 검색만 검출하며 분리나 활용되며는 검출되지 않습니다.
여러 대상을 검출하려면 반복자 반환 검출 함수(re.finditer)를 사용해야 합니다.
re.finditer()는 제 41강에서 다룬 반복자를 반환하므로, 매치 객체를 한꺼번에 만들지 않고 for 문이 요청할 때마다 다음 매치를 하나씩 찾아 줍니다. 위치 정보가 필요하면서 결과가 많을 수 있다면 findall() 대신 finditer()를 사용합니다.
Python 정규 표현식에서 사용할 수 있는 함수는 아래와 같습니다.
정규 표현식 함수
re.compile(pattern, flags=0)패턴(pattern)과 플래그(flags)를 컴파일해서 정규 표현식 객체로 반환합니다.
re.findall(pattern, string, flags=0)입력 문자열(string)에서 패턴(pattern)과 일치하는 문자를 목록으로 반환합니다.
re.finditer(pattern, string, flags=0)입력 문자열(string)에서 패턴(pattern)과 일치하는 문자를 Match 객체 반복자로 반환합니다.
re.split(pattern, string, maxsplit=0, flags=0)입력 문자열(string)에서 패턴(pattern)과 일치하는 문자를 기준으로 최대 분할 크기(maxsplit) 개수만큼 분할해 목록으로 반환합니다.
(만약 최대 분할 크기(maxsplit)에 3을 입력하고 일치하는 문자가 3개 이상이라면, 반환되는 리스트의 길이는 4가 됩니다.)
re.sub(pattern, repl, string, count=0, flags=0)입력 문자열(string)에서 패턴(pattern)과 일치하는 문자를 패턴 최대 반복 수(count)만큼 반복해 repl로 대체해서 문자열로 반환합니다.
(패턴 최대 반복 수(count)가 0일 경우, 횟수 제한 없이 모두 대체합니다.)
re.subn(pattern, repl, string, count=0, flags=0)입력 문자열(string)에서 패턴(pattern)과 일치하는 문자를 패턴 최대 반복 수(count)만큼 반복해 repl로 대체한 뒤, (대체된 문자열, 대체 횟수) 형태의 튜플로 반환합니다.
(패턴 최대 반복 수(count)가 0일 경우, 횟수 제한 없이 모두 대체합니다.)
- Tip : Python 3.13 이상에서는
re.split()의maxsplit,flags와re.sub(),re.subn()의count,flags를 위치 인수로 전달하면DeprecationWarning이 발생합니다.count=2처럼 키워드 인수로 전달합니다.
re.match(pattern, string, flags=0)입력 문자열(string)의 첫 부분에 대해 패턴(pattern)과 일치하는 문자를 Match 객체로 반환합니다.
(없을 경우 None을 반환합니다.)
re.search(pattern, string, flags=0)입력 문자열(string)의 전체에 대해 패턴(pattern)과 일치하는 문자를 Match 객체로 반환합니다.
(없을 경우 None을 반환합니다.)
re.fullmatch(pattern, string, flags=0)입력 문자열(string) 전체가 패턴(pattern)과 일치할 때 Match 객체로 반환합니다.
(없을 경우 None을 반환합니다.)
정규 표현식 함수 활용
앞에서 정리한 함수들을 같은 문자열에 적용해 결과를 비교합니다. 함수마다 반환 형태(리스트, 반복자, 문자열, 튜플, 매치 객체)가 다르다는 점에 주목합니다.
import re
string = "사과: 1000원, 배: 2500원, 귤: 800원"
print(re.findall(r"\d+", string))
for m in re.finditer(r"(\w+): (\d+)원", string):
print(m.group(1), m.group(2), m.span())
print(re.split(r",\s*", string))
print(re.split(r",\s*", string, maxsplit=1))
print(re.sub(r"\d+", "*", string))
print(re.sub(r"\d+", "*", string, count=2))
print(re.subn(r"\d+", "*", string))
print(re.match(r"사과", string))
print(re.match(r"배", string))
print(re.search(r"배", string))
print(re.fullmatch(r"\d{3}-\d{4}", "123-4567"))- 결과
- [‘1000’, ‘2500’, ‘800’]
사과 1000 (0, 9)
배 2500 (11, 19)
귤 800 (21, 28)
[‘사과: 1000원’, ‘배: 2500원’, ‘귤: 800원’]
[‘사과: 1000원’, ‘배: 2500원, 귤: 800원’]
사과: *원, 배: *원, 귤: *원
사과: *원, 배: *원, 귤: 800원
(‘사과: *원, 배: *원, 귤: *원’, 3)
<re.Match object; span=(0, 2), match=’사과’>
None
<re.Match object; span=(11, 12), match=’배’>
<re.Match object; span=(0, 8), match=’123-4567’>
결과를 위에서부터 읽으면 다음과 같습니다.
findall(r"\d+"): 1개 이상 이어진 숫자를 모두 찾아 문자열 리스트로 반환합니다. 결과는 문자열이므로 계산에 사용하려면int()로 변환합니다.finditer(): 이름과 가격을 그룹으로 나눠 매치 객체를 하나씩 반환하며,span()으로 각 항목의 위치를 알 수 있습니다.split(r",\s*"): 쉼표와 그 뒤의 공백 0개 이상을 구분자로 사용하므로,str.split(",")과 달리 항목 앞에 공백이 남지 않습니다.maxsplit=1이면 한 번만 나눠 2개의 항목이 됩니다.sub(): 모든 숫자를*로 바꾼 새 문자열을 반환합니다. 원본string은 바뀌지 않습니다.count=2이면 앞의 2개만 바꿉니다.subn():sub()의 결과와 함께 바꾼 횟수 3을 튜플로 반환합니다.
re.match()는 문자열의 처음부터 일치해야 하므로 배를 찾지 못하지만, re.search()는 문자열 전체에서 찾으므로 배를 검출합니다.
\w는 기본적으로 유니코드 문자와 매치되므로 사과, 배, 귤과 같은 한글도 매치됩니다.
import re
for func in (re.match, re.search, re.fullmatch):
a = func(r"\d+", "123abc")
b = func(r"\d+", "abc123")
c = func(r"\d+", "123")
print(func.__name__, a and a.group(), b and b.group(), c and c.group())- 결과
- match 123 None 123
search 123 123 123
fullmatch None None 123
같은 패턴 \d+를 세 함수에 적용해 차이를 비교한 예제입니다. a and a.group()은 매치가 없으면 None, 있으면 매치된 문자열이 됩니다.
| 함수 | 검사 범위 | 사용 예 |
|---|---|---|
| re.match | 문자열의 시작부터 일치하는 부분 | 문자열이 특정 형식으로 시작하는지 확인 |
| re.search | 문자열 어디든 처음 일치하는 부분 | 문자열 안에 패턴이 있는지 찾기 |
| re.fullmatch | 문자열 전체가 일치 | 입력값이 형식에 맞는지 검증 |
"123abc"에서 match()는 시작 부분의 123을 찾지만, fullmatch()는 뒤의 abc까지 패턴과 일치해야 하므로 실패합니다.
전화번호나 우편번호처럼 입력값 전체가 형식에 맞는지 검사할 때 match()를 사용하면 뒤에 붙은 잘못된 글자를 놓치게 되므로 fullmatch()를 사용합니다.
import re
print(re.split(r"[,;]\s*", "a, b;c,d"))
print(re.split(r"([,;])\s*", "a, b;c"))
print(re.split(r"\s+", " a b "))- 결과
- [‘a’, ‘b’, ‘c’, ‘d’]
[‘a’, ‘,’, ‘b’, ‘;’, ‘c’]
[’’, ‘a’, ‘b’, ‘’]
re.split()은 여러 종류의 구분자를 한 번에 처리할 수 있습니다. 첫 줄은 쉼표 또는 세미콜론 뒤의 공백까지 구분자로 사용했습니다.
두 번째 줄처럼 구분자 패턴에 캡처 그룹이 있으면, 그룹에 매치된 구분자도 결과 리스트에 함께 포함됩니다. 어떤 구분자로 나뉘었는지 알아야 할 때 사용합니다.
세 번째 줄처럼 문자열의 앞뒤가 구분자이면 빈 문자열이 생깁니다. 공백으로 나누는 경우라면 인수 없는 str.split()이 앞뒤 공백을 자동으로 무시하므로 더 간단합니다.
import re
print(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\3/\2/\1", "2026-10-11"))
print(re.sub(r"(?P<y>\d{4})-(?P<m>\d{2})", r"\g<m>월 \g<y>년", "2026-10"))
print(re.sub(r"\d+", lambda m: str(int(m.group()) * 2), "사과 3개, 배 5개"))- 결과
- 11/10/2026
10월 2026년
사과 6개, 배 10개
re.sub()의 바꿀 문자열 repl에서는 \1, \2처럼 그룹 번호로 매치된 내용을 다시 사용할 수 있습니다. 이를 역참조(Backreference)라 합니다. 첫 줄은 연-월-일을 일/월/연 순서로 바꿨습니다.
이름 있는 그룹은 \g<이름>으로 참조합니다. repl에도 역슬래시가 들어가므로 로 문자열로 작성합니다.
세 번째 줄처럼 repl에 함수를 전달하면, 매치될 때마다 매치 객체를 인수로 함수가 호출되고 그 반환값으로 바꿉니다. 숫자를 두 배로 만드는 것처럼 계산이 필요한 치환에 사용합니다.
탐색 문자 활용
탐색(Lookaround)은 문자를 소비하지 않고 현재 위치의 앞이나 뒤에 무엇이 있는지만 검사하는 기능입니다.
“원 앞에 있는 숫자”처럼 조건은 필요하지만 그 조건 문자는 결과에 넣고 싶지 않을 때 사용합니다.
import re
print(re.findall(r"\d+(?=원)", "사과 1000원, 2개"))
print(re.findall(r"(?<=\$)\d+", "$30 and 40"))
print(re.findall(r"(?<!\$)\b\d+", "$30 and 40"))- 결과
- [‘1000’]
[‘30’]
[‘40’]
\d+(?=원)은 뒤에 원이 오는 숫자만 찾으며, 원은 결과에 포함되지 않습니다.
(?<=\$)\d+는 앞에 $가 있는 숫자만 찾고, (?<!\$)\b\d+는 앞에 $가 없는 숫자만 찾습니다.
세 번째 패턴에 \b가 있는 이유는, \b가 없으면 $30의 3은 제외되어도 0은 앞에 $가 아닌 3이 있으므로 0이 따로 검출되기 때문입니다. 단어의 시작에서만 숫자를 찾도록 제한해 이 문제를 막았습니다.
- Tip : Python의 후방 탐색
(?<=...),(?<!...)안에는a+처럼 길이가 변하는 패턴을 사용할 수 없으며,abc나a|b처럼 길이가 정해진 패턴만 사용할 수 있습니다.
import re
pw = re.compile(r"(?=.*[A-Za-z])(?=.*\d).{8,}")
for s in ["abc12345", "abcdefgh", "a1b2"]:
print(s, bool(pw.fullmatch(s)))- 결과
- abc12345 True
abcdefgh False
a1b2 False
전방 탐색을 여러 개 이어 쓰면 여러 조건을 동시에 검사할 수 있습니다. 예제는 “영문자 1개 이상, 숫자 1개 이상, 전체 8자 이상”인 비밀번호를 검사합니다.
(?=.*[A-Za-z])는 문자열 시작 위치에서 “뒤 어딘가에 영문자가 있는가”만 확인하고 위치는 그대로 둡니다. 이어지는 (?=.*\d)도 같은 시작 위치에서 숫자가 있는지 확인하고, 마지막 .{8,}가 실제로 8글자 이상을 매치합니다.
abcdefgh는 숫자가 없어 두 번째 조건에서, a1b2는 길이가 부족해 마지막 조건에서 실패합니다.
플래그 활용
플래그는 패턴 자체를 바꾸지 않고 매칭 규칙을 조정합니다. 같은 패턴이라도 플래그에 따라 결과가 달라지므로, 결과가 예상과 다르다면 플래그부터 확인합니다.
import re
print(re.findall(r"\w+", "Python 3.14 정규식_표현"))
print(re.findall(r"\w+", "Python 3.14 정규식_표현", re.ASCII))
print(re.findall(r"^\w+", "first line\nsecond line", re.MULTILINE))
print(re.findall(r"python", "Python PYTHON python", re.IGNORECASE))- 결과
- [‘Python’, ‘3’, ‘14’, ‘정규식_표현’]
[‘Python’, ‘3’, ‘14’, ‘_’]
[‘first’, ‘second’]
[‘Python’, ‘PYTHON’, ‘python’]
re.ASCII 플래그를 사용하면 \w가 영문자, 숫자, 밑줄만 매치하므로 한글이 제외됩니다.
두 번째 줄에서 정규식_표현 중 밑줄 _만 남은 이유가 이것입니다. 한글이 섞인 데이터에서 영문 단어만 골라내야 한다면 re.ASCII를 사용하거나 [A-Za-z]처럼 직접 범위를 지정합니다.
re.MULTILINE 플래그를 사용하면 ^가 각 행의 처음과 매치되며, re.IGNORECASE 플래그는 대소문자를 구분하지 않습니다.
re.MULTILINE이 없다면 ^는 문자열 전체의 시작에서만 매치되므로 ['first']만 검출됩니다.
import re
phone = re.compile(r"""
(\d{2,3}) # 지역 번호
-
(\d{3,4}) # 국번
-
(\d{4}) # 번호
""", re.VERBOSE)
print(phone.findall("02-123-4567, 010-1234-5678"))- 결과
- [(‘02’, ‘123’, ‘4567’), (‘010’, ‘1234’, ‘5678’)]
re.VERBOSE 플래그를 사용하면 패턴 안의 공백과 줄바꿈이 무시되고, # 뒤를 주석으로 사용할 수 있습니다.
(\d{2,3})-(\d{3,4})-(\d{4})를 한 줄로 쓰는 것과 같은 패턴이지만, 부분마다 의미를 적어 둘 수 있어 긴 패턴을 나중에 수정하기 쉬워집니다.
공백 자체를 찾아야 한다면 \ 나 \s, [ ]처럼 명시적으로 작성합니다.
import re
print(re.findall(r"(?i)python", "Python PYTHON"))
print(re.findall(r"(?i:py)thon", "Python PYTHON"))- 결과
- [‘Python’, ‘PYTHON’]
[‘Python’]
플래그는 함수 인수 대신 패턴 안에 (?i)처럼 인라인 플래그(Inline Flag)로 작성할 수도 있습니다. i는 IGNORECASE, m은 MULTILINE, s는 DOTALL, x는 VERBOSE, a는 ASCII를 의미합니다.
패턴 맨 앞의 (?i)는 패턴 전체에 적용되고, (?i:py)처럼 쓰면 괄호 안에만 적용됩니다. 두 번째 줄은 py만 대소문자를 무시하고 thon은 소문자로 일치해야 하므로 PYTHON은 검출되지 않습니다.
- Tip : 전체에 적용하는 인라인 플래그
(?i)는 패턴의 맨 앞에 작성해야 하며, 중간에 작성하면re.PatternError가 발생합니다.
원자 그룹과 소유 수량자
정규 표현식 엔진은 매치에 실패하면 이전 선택으로 되돌아가 다른 방법을 시도합니다. 이 덕분에 유연한 패턴을 작성할 수 있지만, 경우의 수가 많아지면 검색이 매우 느려질 수 있습니다.
원자 그룹과 소유 수량자는 이 되돌아가기를 금지해 성능 문제를 막는 기능입니다.
import re
print(re.findall(r"a+ab", "aaab"))
print(re.findall(r"(?>a+)ab", "aaab"))
print(re.findall(r"a++ab", "aaab"))
print(re.findall(r"\d++원", "1000원 2000개"))- 결과
- [‘aaab’]
[]
[]
[‘1000원’]
원자 그룹(?>...)과 소유 수량자(*+, ++, ?+, {n,m}+)는 Python 3.11 이상에서 사용할 수 있습니다.
일반적인 수량자는 뒤의 패턴이 일치하지 않으면, 이미 매치한 문자를 하나씩 되돌려주며 다시 시도합니다. 이를 역추적(Backtracking)이라 합니다.
a+ab는 a+가 aaa를 모두 매치한 뒤 ab가 일치하지 않으므로, a 하나를 되돌려주고 aa + ab로 매치에 성공합니다.
반면에 (?>a+)ab와 a++ab는 한 번 매치한 aaa를 되돌려주지 않으므로 매치에 실패합니다.
마지막 줄의 \d++원은 숫자 뒤의 글자가 원인지 확인할 때 숫자를 되돌려줄 필요가 없으므로, 소유 수량자를 사용해도 결과는 일반 수량자와 같습니다. 이처럼 되돌려줘도 결과가 달라지지 않는 곳에 사용하면 결과는 그대로 두고 불필요한 재시도만 줄일 수 있습니다.
이를 활용하면 불필요한 역추적을 막아 과도한 역추적으로 인한 성능 저하를 줄일 수 있습니다.
import re
import time
for n in (18, 20, 22):
start = time.perf_counter()
re.match(r"(a+)+b", "a" * n)
print(n, f"{time.perf_counter() - start:.3f}s")
start = time.perf_counter()
re.match(r"(a++)+b", "a" * 22)
print("possessive", f"{time.perf_counter() - start:.6f}s")- 결과
- 18 0.090s
20 0.263s
22 0.905s
possessive 0.000256s
(a+)+b처럼 수량자 안에 다시 수량자가 있는 패턴은 b가 없는 문자열에서 실패를 확정하기 위해 a를 나누는 모든 방법을 시도합니다.
a가 2개 늘어날 때마다 시간이 약 3배씩 늘어나는 것을 확인할 수 있습니다. a가 30개, 40개라면 사실상 끝나지 않으며, 외부 입력을 검사하는 서비스에서는 이 현상이 ReDoS(정규 표현식 서비스 거부) 공격에 악용될 수 있습니다.
안쪽을 소유 수량자 a++로 바꾸면 한 번 매치한 a를 되돌려주지 않으므로, 같은 입력에서도 즉시 실패합니다.
- Tip : 실행 시간은 컴퓨터의 성능과 실행할 때마다 달라집니다. 시간의 절댓값보다
n이 커질 때 늘어나는 비율을 확인합니다.
패턴 오류(PatternError)
패턴에 괄호의 짝이 맞지 않거나 수량자를 잘못 쓰는 등 문법 오류가 있으면, 패턴을 컴파일하는 단계에서 예외가 발생합니다.
import re
try:
re.compile(r"(abc")
except re.PatternError as e:
print(type(e).__name__, e)
print(re.error is re.PatternError)- 결과
- PatternError missing ), unterminated subpattern at position 0
True
잘못된 정규 표현식 패턴을 컴파일하면 예외가 발생합니다.
Python 3.13 이상에서는 이 예외의 이름이 re.error에서 re.PatternError로 변경되었습니다.
re.error는 하위 호환을 위해 re.PatternError의 별칭으로 남아 있으므로, 기존 코드도 그대로 동작합니다.
오류 메시지의 at position 0은 문제가 시작된 패턴 안의 위치를 알려 줍니다. 예제는 0번 위치의 (가 닫히지 않았다는 뜻입니다.
사용자가 입력한 문자열을 패턴으로 사용하는 프로그램이라면 이 예외를 처리해 두거나, 글자 그대로 찾을 때는 re.escape()를 사용합니다.
문자열의 끝(\z)
문자열의 끝을 나타내는 방법은 $, \Z, \z 세 가지가 있으며, 문자열 끝에 줄바꿈 문자가 있을 때 동작이 다릅니다.
import re
print(re.search(r"end\z", "the end"))
print(re.search(r"end\Z", "the end"))- 결과
- <re.Match object; span=(4, 7), match=’end’>
<re.Match object; span=(4, 7), match=’end’>
Python 3.14 이상에서는 다른 정규 표현식 엔진과의 호환을 위해 문자열의 끝을 의미하는 \z를 사용할 수 있으며, \Z와 같은 의미입니다.
다른 언어의 정규 표현식에서 \z를 사용한 패턴을 그대로 가져와도 Python 3.14부터는 수정 없이 사용할 수 있습니다.
- Tip :
$는 문자열 끝의 줄바꿈 문자 앞에서도 매치되지만,\Z와\z는 문자열의 진짜 끝에서만 매치됩니다.
import re
print(re.search(r"end$", "the end\n"))
print(re.search(r"end\Z", "the end\n"))- 결과
- <re.Match object; span=(4, 7), match=’end’>
None
문자열이 \n으로 끝날 때, $는 마지막 줄바꿈 바로 앞도 끝으로 인정하므로 매치되지만, \Z는 줄바꿈 뒤의 진짜 끝에서만 매치되므로 실패합니다.
파일에서 읽은 줄처럼 끝에 줄바꿈이 붙어 있을 수 있는 문자열을 검증할 때 이 차이로 인해 예상하지 못한 값이 통과할 수 있으므로, 입력값 전체를 엄격하게 검사할 때는 fullmatch()나 \Z를 사용합니다.
정리
| 이름 | 설명 | 반환값/특징 |
|---|---|---|
| re.compile(패턴, 플래그) | 패턴을 컴파일 | 패턴 객체 |
| re.findall(패턴, 문자열) | 일치하는 모든 문자열 검색 | 리스트(그룹 개수에 따라 형태가 다름) |
| re.finditer(패턴, 문자열) | 일치하는 모든 위치 검색 | 매치 객체의 반복자 |
| re.search(패턴, 문자열) | 문자열 어디서든 처음 일치하는 부분 | 매치 객체 또는 None |
| re.match(패턴, 문자열) | 문자열 시작부터 일치 | 매치 객체 또는 None |
| re.fullmatch(패턴, 문자열) | 문자열 전체가 일치 | 매치 객체 또는 None |
| re.split(패턴, 문자열) | 패턴을 기준으로 분할 | 리스트(캡처 그룹은 구분자 포함) |
| re.sub(패턴, 바꿀 값, 문자열) | 일치하는 부분을 치환 | 새 문자열 |
| re.subn(패턴, 바꿀 값, 문자열) | 치환과 횟수 | (새 문자열, 횟수) |
| re.escape(문자열) | 특수 문자 이스케이프 | 문자열 |
- 패턴은 항상 로 문자열
r"..."로 작성합니다. - 수량자는 기본적으로 탐욕적이며,
?를 붙이면 게으른 방식이 됩니다. 데이터의 구조를 고려해 범위를 제한하는 것이 가장 확실합니다. - 결과가 없을 수 있는 함수는
None을 반환하므로 사용하기 전에 확인합니다. - 중첩된 수량자처럼 역추적이 폭발할 수 있는 패턴은 소유 수량자나 원자 그룹으로 제한합니다.
댓글 남기기