8. 정규표현식#
정규표현식regular expression은 패턴 탐색 문자열이며 정규식 또는 레겍스(regex)라고 줄여서 불리기도 한다. 정규표현식은 문자열에 포함되어 있는 특정 패턴의 부분 문자열을 탐색하고 활용할 때 유용하다.
8.1. re 모듈#
정규표현식은 모든 프로그래밍 언어에서 유용하게 활용된다.
파이썬은 re 모듈이 정규표현식 관련 다양한 기능을 제공하며
다음과 같이 불러온 후에 사용할 수 있다.
import re
모듈
파이썬 모듈은 특별한 기능을 제공하는 함수들의 모아놓은 코드 모음집 정도로 이해하면 된다. 모듈의 다양한 활용법은 필요할 때 하나씩 살펴본다.
re 모듈이 제공하는 다음 네 개 함수가 가장 많이 사용된다.
함수 |
기능 |
|---|---|
|
정규표현식에 매칭되는 모든 부분문자열로 구성된 리스트 반환 |
|
정규표현식에 매칭되는 부분문자열을 기준으로 쪼개진 부분문자열들로 구성된 리스트 반환 |
|
정규표현식에 매칭되는 부분문자열이 다른 문자열로 대체된 문자열 반환 |
|
정규표현식에 매칭되는 최초 부분문자열의 정보를 담은 |
findall() 함수 활용 예제
정규표현식 'rains?'는 'rains' 또는 'rain' 과 매칭된다.
's?'에서의 물음표 기호 ?는 왼쪽에 위치한 문자 's'를 0번 또는 1번 사용하는 것을 의미한다.
text = "It rains a lot in rainy season"
re.findall('rains?', text)
['rains', 'rain']
정규표현식에 매칭되는 부분문자열이 없다면 비어 있는 리스트가 반환된다.
text = "It rains a lot in rainy season"
re.findall('raiins?', text)
[]
split() 함수 활용 예제
아래 코드는 'rains'와 'rain'을 기준으로 쪼갠 결과를 보여준다.
text = "It rains a lot in rainy season"
re.split('rains?', text)
['It ', ' a lot in ', 'y season']
쪼개기 횟수를 셋째 인자로 지정할 수도 있다.
한 번 쪼개기
text = "It rains a lot in rainy season"
re.split('rains?', text, 1)
['It ', ' a lot in rainy season']
두 번 쪼개기
text = "It rains a lot in rainy season"
re.split('rains?', text, 2)
['It ', ' a lot in ', 'y season']
정규표현식에 매칭되는 부분문자열이 없다면 쪼갬이 발생하지 않는다.
text = "It rains a lot in rainy season"
re.split('raiins?', text)
['It rains a lot in rainy season']
sub() 함수 활용 예제
아래 코드는 'rain'을 'snow'로 대체한 문자열을 반환한다.
text = "It rains a lot in rainy season"
re.sub('rain', 'snow', text)
'It snows a lot in snowy season'
대체 횟수를 셋째 인자로 지정할 수도 있다.
한 번 대체
text = "It rains a lot in rainy season"
re.sub('rain', 'snow', text, count=1)
'It snows a lot in rainy season'
두 번 대체
text = "It rains a lot in rainy season"
re.sub('rain', 'snow', text, count=2)
'It snows a lot in snowy season'
지정된 정규표현식에 매칭되는 부분문자열이 없다면 문자열 원본 그대로 반환된다.
text = "It rains a lot in rainy season"
re.sub('raiins?', 'snow', text)
'It rains a lot in rainy season'
search() 함수 활용 예제
아래 코드는 정규표현식 'rains?'와 매칭되는 'rains' 또는 'rain'을
주어진 문자열 왼쪽에서부터 찾으며, 가장 먼저 찾은 부분문자열의 정보를
담은 값이 반환된다.
text = "It rains a lot in rainy season"
match = re.search('rains?', text)
찾아진 값의 자료형은 re.Match 자료형의 값이다.
type(match)
re.Match
값은 제대로 보여주지 않는다.
print(match)
<re.Match object; span=(3, 8), match='rains'>
8.2. re.Match 객체#
re.Match 객체, 즉 re.Match 자료형의 값은
정규표현식에 매칭되는 문자열에 대한 정보를 저장할 때 사용되며
아래 기능을 함께 제공한다.
기능 |
설명 |
|---|---|
|
탐색 대상이 되는 문자열 원본을 가리키는 속성 변수 |
|
지정된 정규표현식에 매칭되는 부분문자열의 시작 인덱스와 끝 인덱스로 구성된 튜플을 반환하는 메서드. 단. 끝 인덱스는 실제 끝 인덱스보다 1 큰값으로 지정됨. |
|
지정된 정규표현식에 매칭되는 부분문자열의 시작 인덱스를 반환하는 메서드. |
|
지정된 정규표현식에 매칭되는 부분문자열의 끝 인덱스를 반환하는 메서드. 단. 끝 인덱스는 실제 끝 인덱스보다 1 큰값으로 지정됨. |
|
지정된 정규표현식에 매칭되는 부분문자열을 반환하는 메서드. |
match.string
'It rains a lot in rainy season'
match.span()
(3, 8)
match.start()
3
match.end()
8
match.group(0)
'rains'
지정된 정규표현식에 매칭되는 부분문자열이 없으면 None이 반환된다.
text = "It rains a lot in rainy season"
match = re.search('raiins?', text)
print(match)
None
None 값
None은 어떤 의미도 없음을 의미하는 값이다.
정수 0, 비어 있는 문자열, 비어 있는 리스트 등과 같이 필요에 따라 False로 취급된다.
함수가 반환값을 생성하지 않을 때 반환값으로 None으로 지정한다.
대표적으로 print() 함수의 반환값은 항상 None이다.
나중에 함수를 설명할 때 보다 자세히 설명한다.
8.3. 메타 문자#
문자열의 백슬래시(원화 기호) \가 특정 문자열의 기능을 해제시키는 특별한 기능을 갖는 것처럼
정규표현식에서 특별한 기능을 수행하는 문자를 메타 문자라고 부른다.
먼저 메타 문자를 기능별로 모아서 간단한 활용 예제와 함께 소개한다.
문자 선택
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
영어 소문자 모음 알파벳 중에 하나와 매칭 |
|
영어 대문자 알파벳 C부터 Z까지 중의 하나와 매칭 |
|
|
0부터 9까지의 숫자 기호 하나와 매칭 |
|
|
영어 알파벳 또는 숫자 기호 하나와 매칭 |
|
|
|
|
|
|
영어 소문자 모음 알파벳을 제외한 다른 하나의 문자와 매칭 |
|
길이가 2인 문자열, 단 숫자로 시작하지 않으면서 둘째 문자는 영어 알파벳이어야 함. |
대표 문자
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
|
|
|
|
|
화이트 스페이스 하나와 매칭 |
|
|
화이트 스페이스가 아닌 문자 하나와 매칭 |
|
|
|
|
|
|
|
|
|
|
|
영어 소문자 z로 끝나는 길이가 3인 문자열. 단, |
|
|
숫자 기호가 양끝에 위치한 길이가 5인 문자열. 단, |
문자열 반복
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
문자열 위치
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
주어진 문자열이 |
|
|
주어진 문자열이 |
캐럿caret ^의 기능
캐럿은 두 가지 기능을 갖는다.
첫째,
[^abc]처럼 대괄호로 감싸인 문자 선택 정규표현식에 사용되면 언급된 문자들을 제외시킨다.둘째, 반면에
^abc는abc로 시작하는 문자열과 매칭된다.
문자열 선택
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
|
|
문장이 |
|
|
|
문자열 캡처
메타 문자 |
활용 예제 |
설명 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
캡처와 대체
캡처된 부분문자열은 그룹으로 지정되어 다양한 방식으로 재활용된다. 예를 들어 아래 코드는 주어진 문자열에서 두 개의 부분문자열을 캡처하여 두 개의 그룹으로 지정한다.
re.search()함수 활용
regex = r"([a-zA-Z]+) (\d+)"
match = re.search(regex, "June 24, August 9, Dec 12")
날문자열 활용
정규표현식에 역슬래시가 사용될 경우 날문자열로 지정한다. 그렇지 않으면 다르게 해석될 수도 있다.
먼저 매칭된 전체 부분문자열은 다음과 같다.
match.group(0)
'June 24'
그중에 첫째 그룹은 알파벳으로만 구성된 문자열이다.
match.group(1)
'June'
둘째 그룹은 알파벳으로만 구성된 문자열이다.
match.group(2)
'24'
re.findall()함수 활용
그룹으로 지정된 부분문자열로 구성된 순서쌍들의 리스트가 생성된다.
regex = r"([a-zA-Z]+) (\d+)"
match = re.findall(regex, "June 24, August 9, Dec 12")
match
[('June', '24'), ('August', '9'), ('Dec', '12')]
re.sub()함수 활용
주어진 문자열에서 정규표현식의 그룹에 매칭되는 모든 부분문자열을 지정된 형식의 부분문자열로 대체해서 새로운 문자열을 생성할 때 사용한다.
예를 들어 아래 코드는 June 24 처럼 월과 날짜 형식의 문자열을 모두 날짜 of 월 형식으로
대체한 문자열을 생성한다.
\g<1>: 첫째 캡처 그룹\g<2>: 둘째 캡처 그룹
캡처 그룹의 내용은 앞서 re.findall() 함수의 결과를 참고한다.
regex = r"([a-zA-Z]+) (\d+)"
re.sub(regex, r"\g<2> of \g<1>", "June 24, August 9, Dec 12")
'24 of June, 9 of August, 12 of Dec'
대체 횟수를 셋째 인자로 지정할 수 있다.
regex = r"([a-zA-Z]+) (\d+)"
re.sub(regex, r"\g<2> of \g<1>", "June 24, August 9, Dec 12", count=1)
'24 of June, August 9, Dec 12'
regex = r"([a-zA-Z]+) (\d+)"
re.sub(regex, r"\g<2> of \g<1>", "June 24, August 9, Dec 12", count=2)
'24 of June, 9 of August, Dec 12'
대괄호 안의 메타문자
$, ., ?, *, +, (, ), {, } 는
대괄호 안에 사용되는 경우 메타문자로서의 기능은 해제된다.
반면에
\, [, ], | 등을 문자로서 매칭시키려면 백슬래시와 함께 사용해야 한다.
아래 코드는 메타문자로만 구성된 부분문자열을 매칭시킨다.
regex = r'[\^.+*?$(){}\\[\]\|]+'
re.search(regex, r'?$^.\()[]{}|abc')
<re.Match object; span=(0, 12), match='?$^.\\()[]{}|'>
8.4. 정규표현식 컴파일링#
하나의 정규표현식을 여러 번 활용하려면 먼저 컴파일을 해서 re.RegexObject 자료형의 값으로 만들어 두는 게 편하다.
예를 들어 아래 코드는 (\w+) language를 여러 번 사용하기 위해 먼저 컴파일시켜서 활용한다.
regex = re.compile(r"(\w+) language")
match = regex.search("Python is the easiest language.")
위 코드는 다음과 동일하다.
match = re.search(r"(\w+) language", "Python is the easiest language.")
생성된 Match 객체 또한 동일하게 활용된다.
match = regex.search("Python is the easiest language.")
if match:
print(f"시작: {match.start()}, 끝: {match.end()}")
print(f"매칭 전체 문장: {match.group(0)}")
print(f"매칭 문장: {match.group(1)}")
시작: 14, 끝: 30
매칭 전체 문장: easiest language
매칭 문장: easiest
아래 명령문은 다음과 동일하다.
re.findall(r"(\w+) language", "How many languages can you speak? Two languages or three?")
regex.findall("How many languages can you speak? Two languages or three?")
['many', 'Two']
아래 명령문은 다음과 동일하다.
re.sub(r"(\w+) language", r"\g<1> programming language", "Python is the easiest language.")
즉, 정규표현식에 매칭된 easiest language 대신에 캡처된 easiest를 사용한 easiest programming language를
사용한 문자열이 생성된다.
regex.sub(r"\g<1> programming language", "Python is the easiest language.")
'Python is the easiest programming language.'
8.5. 예제#
다음 예제들은 RegexOne를 많이 참고 하였다.
예제 1
임의의 알파벳으로 구성된 문자열도 정규표현식이다.
아래 세 개의 문자열에 공통으로 포함됨 부분문자열 abc를 정규표현식으로 지정하면
세 개의 문자열 각각에 대해 매칭된다.
매칭 |
문자열 |
|---|---|
성사 |
|
성사 |
|
성사 |
|
답:
알파벳 활용
re.search('abc', 'abcdefg')
<re.Match object; span=(0, 3), match='abc'>
메타문자 활용
re.search(r'\w{3}', 'abcde')
<re.Match object; span=(0, 3), match='abc'>
re.search(r'\w+', 'abc')
<re.Match object; span=(0, 3), match='abc'>
re.search(r'\w{3}', 'abcdefg')
<re.Match object; span=(0, 3), match='abc'>
re.search(r'\w{3}', 'abc')
<re.Match object; span=(0, 3), match='abc'>
예제 2
마침표 기호를 메타문자로서가 아닌 마침표 기호 자체로 매칭할 수 있다.
문자열의 길이가 4이면서 마침표 기호로 끝나는 문자열만 매칭하는 방법을 살펴본다.
마침표 기호 자체는 \. 또는 대괄호 안에 [.]처럼 작성해서 매칭시킨다.
매칭 |
문자열 |
|---|---|
성사 |
|
성사 |
|
성사 |
|
미성사 |
|
답:
re.search(r'.{3}\.', 'cat.')
<re.Match object; span=(0, 4), match='cat.'>
re.search(r'.{3}[.]', '896.')
<re.Match object; span=(0, 4), match='896.'>
re.search(r'.{3}\.', '?=+.')
<re.Match object; span=(0, 4), match='?=+.'>
매칭되지 않는 경우 None이 반환되어 아무런 값이 보이지 않는다.
re.search(r'.{3}\.', 'abc1')
예제 3
정규표현식은 알파벳 대소문자를 구분한다. 또한 하이픈을 이용하여 영어 알파벳과 숫자 기호의 범위를 지정할 수 있다. 다음 예제는 대문자 알파벳 또는 숫자기호를 포함한 경우에만 매칭되도록 한다.
매칭 |
문자열 |
|---|---|
성사 |
|
성사 |
|
성사 |
|
성사 |
|
성사 |
|
미성사 |
|
답:
re.search(r'[ACP|09]', 'Ana')
<re.Match object; span=(0, 1), match='A'>
re.search('[A-Z|0-9]', 'boC')
<re.Match object; span=(2, 3), match='C'>
re.search('[A-Z]', 'cPc')
<re.Match object; span=(1, 2), match='P'>
re.search('[A-Z|0-9]', '9ab')
<re.Match object; span=(0, 1), match='9'>
re.search('[A-Z|0-9]', 'acb0')
<re.Match object; span=(3, 4), match='0'>
re.search('[ABC|09]', 'aax')
예제 4
물음표 기호 ?와 함께 사용되는 문자는 0번 또는 1번 사용될 수 있음을 가리킨다.
예를 들어 file과 files 모두 매칭시키려면 files?를 이용한다.
반면에 물음표 기초 자체는 백슬래시와 함께 표기한다.
매칭 |
문자열 |
|---|---|
성사 |
|
성사 |
|
성사 |
|
미성사 |
|
답:
re.search(r'\d+ files? found\?', '1 file found?')
<re.Match object; span=(0, 13), match='1 file found?'>
re.search(r'\d+ files? found\?', '2 files found?')
<re.Match object; span=(0, 14), match='2 files found?'>
re.search(r'\d+ files? found\?', '24 files found?')
<re.Match object; span=(0, 15), match='24 files found?'>
re.search(r'\d+ files? found\?', 'No files found.')
예제 5
문자열의 처음과 끝에 위치한 문자를 지정할 수 있다.
매칭 |
문자열 |
|---|---|
성사 |
|
미성사 |
|
미성사 |
|
답:
re.search('^Mission.*ful$', 'Mission: successful')
<re.Match object; span=(0, 19), match='Mission: successful'>
re.search('^Mission.*ful$', 'Last Mission: unsuccessful')
re.search('Mission.*ful$', 'Last Mission: unsuccessful')
<re.Match object; span=(5, 26), match='Mission: unsuccessful'>
re.search('^Mission.*ful$', 'Mission: successful upon capture of target')
예제 6
문자열 캡처를 중첩해서 사용할 수도 있다. 아래 코드는 연도와 월 정보뿐만 아니라 연도 정보만 별도로 캡처한다.
그룹 지정 |
문자열 |
그룹 |
|---|---|---|
성사 |
|
|
성사 |
|
|
답:
match = re.search(r'((\w+), \w+\s\d+, (\d+))', 'Monday, Jun 29, 1987')
match.group(0)
'Monday, Jun 29, 1987'
캡처된 그룹의 순서는 소괄호 바깥쪽에서 안쪽으로 진행되며, 왼쪽에 위치한 캡처가 우선된다.
match.group(1)
'Monday, Jun 29, 1987'
match.group(2)
'Monday'
match.group(3)
'1987'
match = re.search(r'((\w+), \w+\s\d+, (\d+))', 'Friday, Apr 04, 2025')
match
<re.Match object; span=(0, 20), match='Friday, Apr 04, 2025'>
match.group(0)
'Friday, Apr 04, 2025'
match.group(1)
'Friday, Apr 04, 2025'
match.group(2)
'Friday'
match.group(3)
'2025'
예제 7
registration_numbers = """
050319-4077931
041008-3899249
"""
답:
regex = r"(\d{6})-\d{7}"
print(re.sub(regex, r"\g<1>-*******", registration_numbers))
050319-*******
041008-*******
예제 8
아래 표에 포함된 각각의 문자열에 대해 성사로 표시된 문자열 전체를 매칭 시키는 하나의 정규표현식을 선언한 다음에 이를 확인하라. 단, 전화번호 앞 세 자리는 캡처되어야 한다.
그룹 지정 |
문자열 |
그룹 |
|---|---|---|
성사 |
|
|
성사 |
|
|
성사 |
|
|
성사 |
|
|
성사 |
|
|
답:
regex = r'(?:\+82)?[\s-]?\(?(\d{3})\)?[\s-]?\d{3}[\s-]?\d{4}'
regex 변수가 가리키는 정규표현식이 성사로 표시된 모든 문자열 전체와 매칭되며
전화번호 맨 앞 세 자리를 캡처한다.
match = re.search(regex, '415-555-1234')
match
<re.Match object; span=(0, 12), match='415-555-1234'>
match.group(1)
'415'
match = re.search(regex, '(416)555-3456')
match
<re.Match object; span=(0, 13), match='(416)555-3456'>
match.group(1)
'416'
match = re.search(regex, '202 555 4567')
match
<re.Match object; span=(0, 12), match='202 555 4567'>
match.group(1)
'202'
match = re.search(regex, '4035555678')
match
<re.Match object; span=(0, 10), match='4035555678'>
match.group(1)
'403'
match = re.search(regex, '+82 416 555 9292')
match
<re.Match object; span=(0, 16), match='+82 416 555 9292'>
match.group(1)
'416'
예제 9
아래 표에 포함된 각각의 문자열에 대해 그룹으로 지정된 부분문자열인 HTML 태그를 캡처하는 정규표현식을 선언한 다음에 이를 확인하라. 또한 정규표현식을 컴파일한 패턴을 이용한다.
그룹 지정 |
문자열 |
그룹 |
|---|---|---|
성사 |
|
|
성사 |
|
|
성사 |
|
|
성사 |
|
|
답:
pattern = re.compile(r'<(\w+)')
regex 변수가 가리키는 정규표현식이 성사로 표시된 모든 문자열 전체와 매칭되며
HTML 태그를 캡처한다.
match = pattern.match('<a>This is a link</a>')
match
<re.Match object; span=(0, 2), match='<a'>
match.group(1)
'a'
match = pattern.match("<a href='https://regexone.com'>Link</a>")
match
<re.Match object; span=(0, 2), match='<a'>
match.group(0)
'<a'
match.group(1)
'a'
match = pattern.match("<div class='test_style'>Test</div>")
match
<re.Match object; span=(0, 4), match='<div'>
match.group(0)
'<div'
match.group(1)
'div'
match = pattern.match("<div>Hello <span>world</span></div>")
match
<re.Match object; span=(0, 4), match='<div'>
match.group(0)
'<div'
match.group(1)
'div'
예제 10
아래 표에 포함된 각각의 문자열에 대해 HTML 태그와 함께 사용된 class와 html 링크 등에 사용된 부분문자열을 캡처하는 정규표현식을 선언한 다음에 이를 확인하라. 또한 정규표현식을 컴파일한 패턴을 이용한다.
그룹 지정 |
문자열 |
그룹 |
|---|---|---|
성사 |
|
|
성사 |
|
|
성사 |
|
|
성사 |
|
답:
pattern = re.compile(r"='([\w:/\.]*)'")
match = pattern.search("<a>This is a link</a>")
match
match = pattern.search("<a href='https://regexone.com'>Link</a>")
match
<re.Match object; span=(7, 30), match="='https://regexone.com'">
match.group(0)
"='https://regexone.com'"
match.group(1)
'https://regexone.com'
match = pattern.search("<div class='test_style'>Test</div>")
match
<re.Match object; span=(10, 23), match="='test_style'">
match.group(0)
"='test_style'"
match.group(1)
'test_style'
match = pattern.search("<div>Hello <span>world</span></div>")
match
예제 11
아래 표에 포함된 각각의 문자열에 대해 성사로 표시된 문자열 전체를 매칭 시키는 하나의 정규표현식을 선언한 다음에 이를 확인하라. 단, 문자열 양끝에 있는 모든 화이트스페이스가 제거된 문자열은 캡처되어야 한다. 또한 정규표현식을 컴파일한 패턴을 이용한다.
그룹 지정 |
문자열 |
그룹 |
|---|---|---|
성사 |
|
|
성사 |
|
|
성사 |
|
|
답:
pattern = re.compile(r'^\s*(([\w\.]+\s)*[\w\.]+)\s*$')
match = pattern.search(' The quick brown fox...')
match
<re.Match object; span=(0, 26), match=' The quick brown fox...'>
match.group()
' The quick brown fox...'
match = pattern.search(' jumps over the lazy dog. \n')
match
<re.Match object; span=(0, 33), match=' jumps over the lazy dog. \n'>
match.group(1)
'jumps over the lazy dog.'
match = pattern.search('\n\t regular expression is the best.')
match
<re.Match object; span=(0, 36), match='\n\t regular expression is the best.'>
match.group(1)
'regular expression is the best.'
8.6. 연습문제#
참고: (연습) 정규표현식