정규 표현식(Regular Expression)
-
정규 표현
- 특정한 패턴과 일치하는 문자열을 '검색', '치환', '제거' 하는 기능 지원
- 정규 표현식의 도움 없이 패턴을 찾는 작업은 불완전 하거나, 작업의 비용이 큼
- ex) 이메일 형식 판별, 전화번호 형식 판별, 숫자로만 이루어진 문자열 등
-
raw string
- 문자열 앞에 r이 붙으면 해당 문자열이 구성된 그대로 문자열로 변환
- 보통 정규 표현식은 raw string을 사용
# raw string
a = 'abcdef\n'
print(a)
b = r'abcdef\n'
print(b)
# abcdef
# abcdef\n
search 함수
-
기본 패턴
-
a, X, 9 등 문자 하나하나의 character 들은 정확히 해당 문자와 일치
-
몇몇 문자들에 대해서는 예외가 존재, 이들은 특별한 의미로 사용
. ^ $ * + ? { } [ ] \ | ( )
-
. (마침표) - 어떤 한 개의 문자와 일치 (newline(엔터) 제외)
-
\w - 문자와 일치 [a-zA-Z0-9_]
-
\s - 공백 문자와 일치
-
\t, \n, \r - tab, newline, return
-
\d - 숫자와 일치 [0-9]
-
^=시작, $=끝 - 각각 문자열의 시작과 끝 의미
-
\가 붙으면 특별한 의미가 없어짐. . = . / \ = \
-
import re
# search 활용
# 찾고자하는 패턴 / 특정 문자열
# 패턴 없는 경우 None 반환
m = re.search(r'abc', '123abcdef')
# match 객체 - m.start(), m.end(), m.group()
# <class 're.Match' >
# 3
# 6
# abc
print(type(m))
print(m.start())
print(m.end())
print(m.group())
# 숫자 2개가 나란히 있는 것이 있는지
# search 는 가장먼저 찾는 패턴을 출력
m = re.search(r'\d\d\w', '112abcdef119')
print(m)
m = re.search(r'..\w\w', '@#!@ABCDabcd')
print(m)
-
메타 캐릭터
- [. ] 내부의 메타 캐릭터는 캐릭터 자체를 나타냄
[abck] : a or b or c or k
[abc.^] : a or b or c or . or ^
[a-d] : -와 함께 사용 되면 해당 문자 사이의 범위에 속하는 문자 중 하나
[0-9] : 모든 숫자
[a-z] : 모든 소문자
[A-Z] : 모든 대문자
[a-zA-Z0-9] : 모든 알파벳 문자 및 숫자
[^0-9] : ^가 맨 앞에 사용 되는 경우 해당 문자 패턴이 아닌 것과 매칭
- [. ] 내부의 메타 캐릭터는 캐릭터 자체를 나타냄
print(re.search(r'[cbm]at', 'cat'))
print(re.search(r'[cbm]at', 'bat'))
print(re.search(r'[0-3]haha', '1haha'))
print(re.search(r'[abc.^]aron', '^aron'))
print(re.search(r'[^abc]aron', 'daron'))
-
\ 활용
- \d : 모든 숫자 [0-9]
- \D : 숫자가 아닌 문자 [^0-9]
- \s : 공백 문자(띄어쓰기, 탭, 엔터 등)
- \S : 공백이 아닌 문자
- \w : 알파벳 대소문자, 숫자와 동일 [0-9a-zA-Z]
- \W : non alpha-numeric 문자 [^0-9a-zA-Z]
- ., \ : 메타 캐릭터 자체 표현 → . , \
-
.
- 모든 문자
-
반복 패턴
-
'+' - 1번 이상의 패턴이 발생
-
'*' - 0번 이상의 패턴이 발생
-
'?' - 0 혹은 1번의 패턴이 발생
-
반복 패턴의 경우 greedy하게 검색. 즉 가능한 많은 부분이 매칭 되도록 함
a[bcd]*b 패턴을 abcbdccb 에서 검색
→ ab, abcb, abcbdccb 전부 가능하지만 최대 값인 abcbdccb 검색됨
-
-
^*, *$
- ^ 문자열의 맨 앞부터 일치하는 경우 검색
- $ 문자열의 맨 뒤부터 일치하는 경우 검색
# b로 시작하고 어떤 숫자나 문자가 1번이상 반복 후 a로 끝남
# <re.Match object; span=(0, 6), match='banana'>
print(re.search(r'b\w+a', 'banana'))
# ii 출력 - 문자열 첫번째 인덱스부터 시작해서 가장 빨리 매치되는 것만 찾음
re.search(r'i+', 'piigiii')
# 검출 실패
re.search(r'pi+g', 'pg')
# 검출 성공
re.search(r'pi*g', 'pg')
# s가 한번 있거나 없거나
re.search(r'https?', 'https://www.naver.com')
# <re.Match object; span=(2, 6), match='bana'>
print(re.search(r'b\w+a', 'cabana'))
# 문자열 시작이 c이므로 검출안됨
print(re.search(r'^b\w+a', 'cabana'))
# b로 시작하고 a로 끝나는 문자
print(re.search(r'b\w+a$', 'cabana'))
- grouping
- ( ) 을 사용하여 그루핑
- 매칭 결과를 각 그룹별로 분리 가능
- 패턴 명시 할 때, 각 그룹을 괄호 안에 넣어 분리하여 사용
- 만들어진 패턴에서 ()로 그루핑해서 사용 추천
# <re.Match object; span=(0, 14), match='test@gmail.com'>
print(re.search(r'\w+@.+', 'test@gmail.com'))
# test@gmail.com
# test
# gmail.com
m = re.search(r'(\w+)@(.+)', 'test@gmail.com')
print(m.group(0))
print(m.group(1))
print(m.group(2))
-
{ } - 반복 횟수 명시
- *, +, ? 을 사용하여 반복적인 패턴을 찾는 것이 가능하나, 반복의 횟수 제한은 불가
- 패턴 뒤에 위치하는 중괄호 { } 에 숫자를 명시 하면 해당 숫자 만큼의 반복인 경우에만 매칭
- {4} - 4번 반복
- {3,4} - 3~4번 반복
-
미니멈 매칭(non-greedy way)
- 기본적으로 *, +, ? 를 사용하면 greedy하게 동작
- *?, +? 을 이용하여 해당 기능을 구현
# <re.Match object; span=(0, 17), match='<html>haha</html>'>
print(re.search(r'<.+>', '<html>haha</html>'))
# 미니멈 매칭 - <re.Match object; span=(0, 6), match='<html>'>
print(re.search(r'<.+?>', '<html>haha</html>'))
# <re.Match object; span=(0, 5), match='aaaaa'>
# <re.Match object; span=(0, 3), match='aaa'>
print(re.search(r'a{3,5}', 'aaaaa'))
print(re.search(r'a{3,5}?', 'aaaaa'))
match 함수
- search와 유사하나, 주어진 문자열의 시작부터 비교하여 패턴이 있는지 확인
- 시작부터 해당 패턴이 존재하지 않는다면 None 반환
# None
# <re.Match object; span=(0, 3), match='123'>
print(re.match(r'\d\d\d', 'my number is 123'))
print(re.match(r'\d\d\d', '123 is my number'))
findall 함수
- search가 최초로 매칭되는 패턴만 반환한다면, 이 함수는 매칭되는 전체 패턴 반환
- 매칭 되는 모든 결과를 리스트 형태로 반환
# ['test@gmail.com', 'test2@gmail.com']
# ['test@gmail', 'test2@gmail']
print(re.findall(r'[\w-]+@[\w.]+', 'test@gmail.com haha test2@gmail.com nice test'))
print(re.findall(r'[\w-]+@[\w]+', 'test@gmail.com haha test2@gmail.com nice test'))
sub 함수
- 주어진 문자열에서 일치하는 모든 패턴을 replace
- 그 결과를 문자열로 다시 반환
- 두번째 인자는 특정 문자열이 될 수도 있고, 함수가 될 수도 있음
- count가 0인 경우는 전체를, 1이상이면 해당 숫자만큼 치환
# great.com haha great.com nice test
print(re.sub(r'[\w-]+@[\w]+', 'great',
'test@gmail.com haha test2@gmail.com nice test'))
# great.com haha test2@gmail.com nice test
print(re.sub(r'[\w-]+@[\w]+', 'great',
'test@gmail.com haha test2@gmail.com nice test', count=1))
compile 함수
- 동일한 정규 표현식을 매번 다시 쓰기 번거로움을 해결
- compile 로 해당 표현식을 re.RegexObject 객체로 저장하여 사용 가능
# <re.Match object; span=(0, 10), match='test@gmail'>
email_reg = re.compile(r'[\w-]+@[\w]+')
print(email_reg.search('test@gmail.com haha'))
연습
email_reg = re.compile(r'[\w-]+@[\w.]+')
# test@gmail.com. 방지위해 변경
email_reg = re.compile(r'[\w-]+@[\w.]+\w+')
webs = [
'http://www.test.co.kr',
'htp://www.test.co.kr',
'ttp://www.test.co.kr',
'https://www.test.com',
'https://www.test.com.',
'https:/www.test.com',
'https//www.test.com',
]
# 문자로 한번이상 반복되면서 끝남
web_reg = re.compile(r'https?://[\w.]+\w+$')
# [True, False, False, True, False, False, False]
print(list(map(lambda w: web_reg.search(w) != None, webs)))