

Python 정규표현식 정복하기!
왜 정규표현식을 알아야 하나!?
정규표현식은 문자열을 가공할 때 많이 사용됩니다.
정규표현식을 모르고 사용했을 때 문자열을 파싱하는게 매우 어려웠습니다..
그러므로 모르고 고생하지 말고 이번에 제대로 정복해봅시다!
정규표현식
정규표현식이란?
정규표현식(Regular Expression)은 문자열 패턴을 검색하거나 변경하기 위한 문자열 표현 방법입니다. 문자열 패턴은 일련의 문자열과 특수 문자(메타 문자)로 정의됩니다. 이러한 패턴은 문자열에서 일치하는 문자열 또는 패턴을 찾거나 변경하는 데 사용됩니다.
🟢 정규표현식의 장점
- 유연성: 정규표현식은 다양한 문자열 패턴을 정의할 수 있으므로, 문자열을 검색하거나 변경할 때 매우 유연합니다.
- 표현력: 정규표현식은 간결하고 강력합니다. 간단한 패턴에서부터 복잡한 패턴까지 표현할 수 있으며, 이를 통해 효과적인 문자열 처리를 가능하게 합니다.
- 효율성: 정규표현식은 문자열 처리를 위해 최적화되어 있으므로, 매우 빠르게 문자열을 처리할 수 있습니다.
- 다양한 언어에서 지원: 대부분의 프로그래밍 언어와 Unix/Linux 명령어에서 정규표현식을 지원하므로, 다양한 환경에서 사용할 수 있습니다.
🔴 정규표현식의 단점
- 학습 곡선: 정규표현식은 처음에는 이해하기 어려울 수 있습니다. 복잡한 패턴을 작성하려면 일정한 수준의 학습이 필요합니다.
- 가독성: 매우 복잡한 정규표현식은 이해하기 어려울 수 있습니다. 따라서 코드를 작성할 때 가독성이 저하될 수 있습니다.
- 불안정성: 매우 복잡한 정규표현식은 정규표현식 엔진에서 불안정성을 유발할 수 있습니다. 이는 처리 속도가 느리거나, 충돌이 발생할 수 있음을 의미합니다.
- 적용 범위: 정규표현식은 특정한 패턴을 찾는 데 효과적이지만, 모든 문제에 적용할 수 있는 것은 아닙니다. 때로는 다른 방법이 더 효과적일 수 있습니다.
정규표현식을 쓸 때 유용한 곳
- 문자열 검색: 특정한 문자열을 찾는데 매우 유용합니다. 예를 들어, 이메일 주소, 전화번호, URL 등의 패턴을 검색할 때 사용할 수 있습니다.
- 문자열 대체: 문자열에서 특정한 문자열을 다른 문자열로 대체할 때 유용합니다. 예를 들어, 특정한 단어나 문장을 다른 단어나 문장으로 대체할 때 사용할 수 있습니다.
- 입력 유효성 검사: 입력값이 특정한 규칙을 만족하는지 검사할 때 사용됩니다. 예를 들어, 이메일 주소나 비밀번호 등의 입력값이 유효한지 검사할 때 사용할 수 있습니다.
- 로그 분석: 로그 파일에서 특정한 패턴을 검색하거나, 원하는 정보를 추출할 때 사용됩니다.
- 데이터 추출: 문자열에서 특정한 데이터를 추출할 때 매우 유용합니다. 예를 들어, HTML 태그에서 특정한 데이터를 추출하는데 사용할 수 있습니다.
- 파일 이름 필터링: 특정한 파일 이름 패턴을 필터링할 때 사용됩니다. 예를 들어, 특정한 확장자를 가진 파일이나 특정한 파일 이름 패턴을 가진 파일을 찾을 때 사용할 수 있습니다.
자주 쓰이는 정규표현식 패턴
먼저 자주쓰이는 패턴을 학습하고 그 패턴이 어떻게 구성되어 있는지 보겠습니다.
정규표현식 기호 모음은 아래에 서술했으니 아래로 가시면 확인하실 수 있습니다!
📞 전화번호 정규표현식 패턴
import re
# 한국 휴대폰 전화번호 정규표현식 패턴
import re
# 한국 휴대폰 전화번호 정규표현식 패턴
pattern = re.compile(r'010[-]?(\d{3,4})[-]?(\d{4})')
# 텍스트에서 한국 휴대폰 전화번호 찾기
text = "안녕하세요! 저의 전화번호는 010-1234-5678입니다."
match = pattern.search(text)
if match:
print("찾은 전화번호:", match.group()) # 찾은 전화번호: 010-1234-5678
else:
print("전화번호를 찾지 못했습니다.")이 코드는 대시(-)나 공백 문자를 구분자로 사용한 경우도 포함한 휴대폰 전화번호를 찾을 수 있습니다. 텍스트에서 찾은 전화번호를 출력하거나 다른 작업을 수행할 수 있습니다.
이걸 이제 하나씩 뜯어보겠습니다!물론이죠. 정규표현식은 복잡해 보일 수 있지만, 각 부분을 따로 살펴보면 이해하기 쉽습니다. 아래에서 해당 정규표현식 패턴을 하나씩 설명하겠습니다:
r'010[-]?(\d{3,4})[-]?(\d{4})'-
r: Python에서 정규표현식 앞에r문자를 붙이는 것은 이 문자열이 raw string임을 나타냅니다. 이것은 문자열 내에서 역슬래시\가 이스케이프 문자로 해석되지 않게 하기 위함입니다. -
'010: 문자열이 ‘010’으로 시작해야 함을 나타냅니다. -
[-]?: 대괄호[]내의 문자 중 하나와 일치하며, 이 경우에는 대시-만 있습니다. 뒤의 물음표?는 바로 앞의 문자나 그룹이 0번 또는 1번 나타남을 의미합니다. 즉, 이 표현식은 대시-가 없거나 한 번만 나타날 수 있음을 나타냅니다. -
(\d{3,4}):\d는 숫자를 의미하고,{3,4}는 앞의 문자가 3번 또는 4번 반복될 수 있음을 의미합니다. 따라서 이 부분은 3자리 또는 4자리 숫자를 의미합니다. 괄호()는 이 부분을 그룹으로 만들어서, 나중에 이 부분을 따로 참조할 수 있게 합니다. -
[-]?: 앞서 설명한 것과 동일하게, 대시-가 없거나 한 번만 나타날 수 있음을 나타냅니다. -
(\d{4}): 이 부분은 4자리 숫자를 의미합니다. 마찬가지로 괄호()는 이 부분을 그룹으로 만들어서, 나중에 이 부분을 따로 참조할 수 있게 합니다.
따라서 이 정규표현식은 ‘010’으로 시작하고, 그 다음에는 대시 -가 없거나 있을 수 있고, 그 다음에는 3자리 또는 4자리 숫자가 있으며, 다시 대시 -가 없거나 있을 수 있고, 마지막으로 4자리 숫자가 있는 문자열을 찾습니다.
✉ 이메일 정규표현식 패턴
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?$'
def is_valid_email(email):
if re.match(email_pattern, email):
return True
else:
return False
if is_valid_email('example@example.com'):
print('Valid email') # Valid email
else:
print('Invalid email')이메일 주소의 정규표현식 패턴을 하나씩 설명해보겠습니다.
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(?:\.[a-zA-Z]{2,})?$'^: 문자열의 시작[a-zA-Z0-9._%+-]+: 1글자 이상의 영문 대소문자, 숫자, 밑줄(_), 마침표(.), 퍼센트(%), 더하기(+), 하이픈(-)으로 이루어진 문자열@: @ 기호[a-zA-Z0-9.-]+: 1글자 이상의 영문 대소문자, 숫자, 마침표(.), 하이픈(-)으로 이루어진 문자열\.: 마침표(.) 문자[a-zA-Z]{2,}: 2글자 이상의 영문 대소문자로 이루어진 문자열 (TLD)(?:\.[a-zA-Z]{2,})?:[a-zA-Z]{2,}패턴을 또 반복하는데,?는 0회 또는 1회 반복을 의미합니다. 이를 통해 도메인 이름이co.kr과 같은 형태일 경우도 매칭할 수 있습니다.$: 문자열의 끝
이 패턴을 사용하여 검증하려는 이메일 주소가 정규표현식 패턴과 일치하는지 검사할 때는 re 모듈의 search()나 match() 메서드 등을 사용할 수 있습니다.
수식 분리하기
이번에는 제가 코딩테스트 문제를 풀다가 마주했던 문제에 대해서 잠시 설명하겠습니다.
수식이 100-200*300-500+20으로 주어졌을 때 ['100', '-', '200', '*', '300', '-', '500', '+', '20']으로 분리하는 과정이 필요했습니다.
이 때도 정규표현식을 생각하지 못해서 상당히 어렵게 구현했습니다.
import re
expression = '100-200*300-500+20'
tokens = re.split(r'(\D)', expression)
print(tokens)
# ['100', '-', '200', '*', '300', '-', '500', '+', '20']- 정규표현식에서 괄호는 그룹을 나타내는 역할을 합니다. 따라서 괄호로 괄호로 둘러싼 부분은 하나의 그룹으로 처리되며,
split()메소드는 그룹을 포함한 구분자를 기준으로 문자열을 분리합니다. - 위 코드에서는
\D를 그룹으로 둘러싸서 하나의 그룹으로 처리하였습니다. 따라서 숫자와 숫자가 아닌 문자가 번갈아가면서 그룹을 처리되게 하며,split()메소드는 이를 구분자로 문자열을 분리합니다. - 결과적으로 리스트의 요소는 숫자와 숫자가 아닌 문자가 번갈아가면서 저장됩니다.
파이썬 정규표현식 re 모듈
Python에서 정규표현식을 사용하기 위해서는 re 모듈을 import하여 사용합니다. 이 모듈에서 제공하는 메서드들과 각 메서드의 예시를 함께 정리해보겠습니다.
문자열의 시작부분부터 패턴이 매치되는지 확인하는 함수(re.match)
re.match(pattern, string)
import re
print(re.match('abc', 'abcdef')) # <re.Match object; span=(0, 3), match='abc'>
print(re.match('abc', 'abcdefabc')) # <re.Match object; span=(0, 3), match='abc'>
print(re.match('abc', 'abcdefabcabc')) # <re.Match object; span=(0, 3), match='abc'>
print(re.match('abc', 'ababcdef')) # None이 함수는 매치되면 match 객체를 반환하고, 그렇지 않으면 None을 반환합니다.
문자열 전체에서 패턴이 매치되는 부분을 찾는 함수(re.search)
re.search(pattern, string)
import re
print(re.search('abc', 'abcdef')) # <re.Match object; span=(0, 3), match='abc'>
print(re.search('abc', 'abcdefabc')) # <re.Match object; span=(0, 3), match='abc'>
print(re.search('abc', 'ababcdefabc')) # <re.Match object; span=(2, 5), match='abc'>처음으로 매치되는 부분의 match 객체를 반환합니다. 매치되는 부분이 없으면 None을 반환합니다.
문자열 전체에서 패턴에 매치되는 모든 부분을 찾아 리스트로 반환하는 함수(re.findall)
re.findall(pattern, string)
import re
print(re.findall('abc', 'abcdefabcabc')) # ['abc', 'abc', 'abc']문자열 내에서 패턴에 매치되는 부분을 다른 문자열로 교체하는 함수(re.sub)
re.sub(pattern, repl, string)
import re
print(re.sub('abc', '123', 'abcdefabcabc')) # '123def123123'이 외에도 다른 함수들이 많지만 나머지는 python Document를 보시면 될 것 같습니다.
정규식 플래그
파이썬 re 모듈에서 사용되는 주요 정규식 플래그는 다음과 같습니다
| 플래그 | 설명 |
|---|---|
re.IGNORECASE 또는 re.I |
대소문자를 구분하지 않습니다. |
re.MULTILINE 또는 re.M |
^와 $ 메타문자를 각각 문자열의 시작과 끝, 그리고 개행문자로 구분된 각 줄의 시작과 끝으로 인식합니다. |
re.DOTALL 또는 re.S |
. 메타문자가 개행문자를 포함한 모든 문자와 매치되도록 합니다. |
re.VERBOSE 또는 re.X |
정규표현식을 더 읽기 쉽게 만들 수 있도록 해주며, 라인을 분리하고 주석을 추가하는 것이 가능합니다. |
re.ASCII 또는 re.A |
\b, \B, \d, \D, \s, \S, \w, \W 등과 같은 특수 문자 시퀀스가 ASCII 문자에 대해서만 작동하도록 합니다. |
각 플래그는 re.compile(pattern, flags) 형식의 선택적 flags 매개변수로 전달할 수 있으며, re.match(), re.search() 등의 메소드에서도 사용될 수 있습니다. 또한 플래그를 | 연산자를 사용하여 결합할 수 있습니다. 예를 들어, 대소문자를 구분하지 않으며 여러 줄의 텍스트를 처리하려면 다음과 같이 사용할 수 있습니다:
import re
pattern = re.compile('abc', re.I | re.M)
matches = pattern.findall('ABC\nabc\nAbC')
print(matches) # ['ABC', 'abc', 'AbC']정규식 특정 문자 숫자 매칭 패턴
정규식에서 특정 문자나 숫자를 매치하는 데 사용되는 주요 패턴들은 다음과 같습니다
| 패턴 | 설명 |
|---|---|
\d |
모든 숫자와 일치합니다. [0-9]와 동일합니다. |
\D |
숫자를 제외한 모든 문자와 일치합니다. [^0-9]와 동일합니다. |
\s |
공백 문자(스페이스, 탭, 줄바꿈 등)와 일치합니다. |
\S |
공백 문자를 제외한 모든 문자와 일치합니다. |
\w |
모든 알파벳 문자, 숫자, 밑줄과 일치합니다. [a-zA-Z0-9_]와 동일합니다. |
\W |
\w에 해당하는 문자를 제외한 모든 문자와 일치합니다. [^a-zA-Z0-9_]와 동일합니다. |
^ |
문자열이나 행의 시작 부분과 일치합니다. |
$ |
문자열이나 행의 끝 부분과 일치합니다. |
[abc] |
괄호 안의 문자 중 하나와 일치합니다. a, b, c 중 어느 하나와 일치하는 패턴을 의미합니다. |
[^abc] |
괄호 안의 문자들을 제외한 모든 문자와 일치합니다. a, b, c를 제외한 모든 문자와 일치하는 패턴을 의미합니다. |
[a-z] |
지정된 범위 내의 문자 중 하나와 일치합니다. 이 경우 소문자 a에서 z까지 모든 문자와 일치합니다. |
[A-Z] |
지정된 범위 내의 문자 중 하나와 일치합니다. 이 경우 대문자 A에서 Z까지 모든 문자와 일치합니다. |
[0-9] |
지정된 범위 내의 문자 중 하나와 일치합니다. 이 경우 숫자 0에서 9까지 모든 숫자와 일치합니다. |
이러한 패턴들을 조합하여 복잡한 문자열 검색 및 처리 작업을 수행할 수 있습니다. 예를 들어, 이메일 주소나 전화번호와 같은 특정 패턴을 가진 문자열을 찾거나, 텍스트에서 특정 형태의 정보를 추출하는 데 이러한 패턴들이 사용됩니다.
정규식 갯수 반복 패턴
정규식에서 검색 기준을 설정하는데 사용되는 주요 패턴들은 다음과 같습니다
| 패턴 | 설명 |
|---|---|
* |
앞선 문자나 그룹이 0번 이상 반복되는 것과 일치합니다. |
+ |
앞선 문자나 그룹이 1번 이상 반복되는 것과 일치합니다. |
? |
앞선 문자나 그룹이 0번 또는 1번 등장하는 것과 일치합니다. |
{n} |
앞선 문자나 그룹이 정확히 n번 반복되는 것과 일치합니다. |
{n,} |
앞선 문자나 그룹이 n번 이상 반복되는 것과 일치합니다. |
{,n} |
앞선 문자나 그룹이 n번 이하 반복되는 것과 일치합니다. |
{n,m} |
앞선 문자나 그룹이 최소 n번 이상, 최대 m번 이하 반복되는 것과 일치합니다. |
a|b |
a 또는 b와 일치합니다. |
이러한 패턴들은 텍스트에서 특정 패턴을 가진 문자열을 찾거나 텍스트를 분석하는 데 사용됩니다. 예를 들어, 이메일 주소의 형식이나 전화번호와 같은 특정 패턴을 찾을 때 사용할 수 있습니다. 또한 텍스트에서 특정 형식의 데이터를 추출하는 데도 사용됩니다.
아래는 이 패턴들을 사용한 예제입니다:
import re
# '+'를 사용한 예제
print(re.findall('a+', 'aaaaaabcaaa')) # ['aaaaaa', 'a', 'aaa']
# '*'를 사용한 예제
print(re.findall('a*', 'aaaaaabcaaa')) # ['aaaaaa', '', '', '', '', 'a', 'aaa', '']
# '?'를 사용한 예제
print(re.findall('a?', 'aaaaaabcaaa')) # ['a', 'a', 'a', 'a', 'a', 'a', '', 'a', 'a', 'a', '']
# '{n}'를 사용한 예제
print(re.findall('a{2}', 'aaaaaabcaaa')) # ['aa', 'aa', 'aa', 'aa']
# '{n,}'를 사용한 예제
print(re.findall('a{2,}', 'aaaaaabcaaa')) # ['aaaaaa', 'aaa']
# '{,n}'를 사용한 예제
print(re.findall('a{,2}', 'aaaaaabcaaa')) # ['', 'aa', 'aa', 'aa', '', '', 'aa', 'a', '']
# '{n,m}'를 사용한 예제
print(re.findall('a{2,4}', 'aaaaaabcaaa')) # ['aaaa', 'aaa']
# '|'를 사용한 예제
print(re.findall('a|b', 'abcd')) # ['a', 'b']정규식 그룹화 패턴
정규식에서 그룹을 생성하고 참조하는 데 사용되는 주요 패턴들은 다음과 같습니다
| 패턴 | 설명 |
|---|---|
( ) |
괄호 안의 패턴을 그룹으로 만들어줍니다. 이를 통해 패턴의 일부분을 하나의 단위로 취급할 수 있게 해줍니다. |
\n |
n번째 그룹에 해당하는 부분과 동일한 문자열에 일치합니다. n은 1부터 시작하는 그룹의 인덱스입니다. |
(?P<name> ) |
이름이 name인 그룹을 생성합니다. |
(?P=name) |
이름이 name인 그룹에 해당하는 부분과 동일한 문자열에 일치합니다. |
아래는 이 패턴들을 사용한 예제입니다:
import re
# '( )'를 사용한 예제
pattern = re.compile('(a)(b)(c)')
match = pattern.match('abc')
print(match.group(0)) # 'abc'
print(match.group(1)) # 'a'
print(match.group(2)) # 'b'
print(match.group(3)) # 'c'
# '\n'를 사용한 예제
pattern = re.compile('(a)\\1')
match = pattern.match('aa')
print(match.group(0)) # 'aa'
print(match.group(1)) # 'a'
# '(?P<name> )'와 '(?P=name)'을 사용한 예제
pattern = re.compile('(?P<first>a)(?P=first)')
match = pattern.match('aa')
print(match.group(0)) # 'aa'
print(match.group('first')) # 'a'이러한 패턴들은 텍스트에서 특정 패턴을 가진 문자열을 찾거나 텍스트를 분석하는 데 사용됩니다. 예를 들어, 이메일 주소의 형식이나 전화번호와 같은 특정 패턴을 찾을 때 사용할 수 있습니다. 또한 텍스트에서 특정 형식의 데이터를 추출하는 데도 사용됩니다.
끝으로 마지막 정규표현식을 연습할 수 있는 간단한 문제와 문제집을 남겨놓고 가겠습니다.
백준 모음의 개수
백준 피카츄
정규표현식 문제집
참고 게시글: 인파님 블로그