Data/Python

[Python] 파이썬 정규 표현식 - re

재은초 2023. 6. 18. 15:51
반응형

정규표현식(regular expression)

특정한 패턴과 일치하는 문자열를 '검색/치환/제거'하는 기능을 지원
정규표현식의 도움없이 패턴을 찾는 작업은 불완전하거나, 작업 비용이 높음
예를 들어, 이메일 형식 판별, 전화번호 형식 판별, 숫자로만 이루어진 문자열 등

# re 패키지 사용법
import re
re.함수(찾아줄것, 찾아야할 데이터) - 직접 찾아줄 것을 대입

기본 패턴

  • a, X, 9 등등 문자 하나하나의 character들은 정확히 해당 문자와 동일. 대소문자의 경우 기본적으로 구별하나, 구별하지 않도록 설정 가능. 몇몇 문자들에 대해서는 예외가 존재하는데, '. ^ $ * + ? { } [ ] \ | ( )'들은 틀별한 의미로 사용 됨
  • https://docs.python.org/3/library/re.html
  1. [abc] - a, b, c중 한 개의 문자와 매치  - a : 매치여부 OK  - apple : ok  - double : no
  2. \d : 숫자 0부터 9 사이 아무거나와 매치, [0-9]와 동일
  3. \D : 숫자가 아닌 것과 매치 [^0-9]와 동일
  4. \s : 공백(whitespace)문자와 매치
  5. \S : 공백(whitespace)문자가 아닌것과 매치,
  6. \w : 문자 + 숫자와 매치, [a-zA-Z0-9]와 동일
  7. \W : 문자+숫자가 아닌 문자와 매치. [^a-zA-Z0-9]와 동일
  8. a.b : a와 b 사이에 줄바꿈 문자를 제외한 모든 문자 허용
  9. a[.]b : a와 b 사이에 dot 문자만 허용
  10. ca*t  : a 문자가 0번 이상 반복 허용
  11. ca+t :  a 문자가 1번 이상 반복 허용
  12. ca?t : a 문자가 없거나, 1번만 허용
  13. ca{3}t : a 문자가 3번 반복되면 매치
  14. ca{2, 3}t : a 문자가 2~3번 반복되면 매치
  15. .(마침표): 어떤 한개의 문자와도 일치. 즉, 모든 문자를 의미 (newline(엔터) 제외)
  16. \t, \n, \r: tab, newline, return
  17. ^: 시작, $: 끝 각각 문자열의 시작과 끝을 의미
  18. \가 붙으면 스페셜한 의미가 없어짐. 예를들어 \.는 .자체를 의미 \\는 \를 의미

문자로 변환 r

문자열 앞에 r이 붙으면 해당 문자열이 구성된 그대로 문자열로 변환

a = 'abcdef\n'            # \n = 엔터
print(a)

b = r'abcdef\n'           # r'문자열' = raw string : 문자열 그대로 변환
print(b)


메타 캐릭터

메타 캐릭터란, 프로그래밍 언어 등에서 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자를 말한다
cf. \를 앞에 붙여 원래 의미를 벗어나는 escape 문자들

( )

()을 사용하여, 매칭 결과를 각 그룹별로 분리 가능
패턴 명시 할 때, 각 그룹을 괄호() 안에 넣어 분리하여 사용

m = re.search(r'\w+@.+', 'test@gmail.com')
m.group()                                # 그룹 호출하면 문자열 전체 호출
------------------------------
m = re.search(r'(\w+)@(.+)', 'test@gmail.com')
print(m.group(0))                        # 0번은 전체값
print(m.group(1))                        # 1번은 첫번째 괄호
print(m.group(2))                        # 2번은 두번째 괄호

[ ]

보통 문자들의 범위를 나타내기 위해 사용하며, 원래 특수의미로 사용하는 것도 []안에 들어가면 원래 의미로 사용됨
[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] : ^(not)가 맨 앞에 사용 되는 경우 해당 문자 패턴이 아닌 것과 매칭

>>> print(re.search(r'[cbm]at', 'aat'))       # cbm 중 하나 시작 at로 끝 
>>> print(re.search(r'[0-9]hahah', '1hahah')) # 숫자 중 하나 시작 hahah로 끝
>>> print(re.search(r'[abc.^]aron', 'caron')) # abc.^ 중 하나로 시작
>>> print(re.search(r'[^abc]aron', 'aaron'))  # aaron,baron,caron 아닌것
None
<re.Match object; span=(0, 6), match='1hahah'>
<re.Match object; span=(0, 5), match='caron'>
None

\

다른 문자와 함께 사용되어 특수한 의미를 지니거나

\d : 숫자 [0-9]와 동일
\D : 숫자가 아닌 문자 [^0-9]와 동일
\s : 공백 문자 (띄어쓰기, 탭, 엔터 등)
\S : 공백이 아닌 문자
\w : 알파벳대소문자, 숫자 [0-9a-zA-Z]와 동일
\W : non alpha-numeric 문자 [^0-9a-zA-Z]와 동일

글자 자체를 표현하도록 할 경우 사용

\. , \\

re.search('\sand', 'apple and banana')  # 띄어쓰기와 and 문자
------------------------------
re.search(r'\.and', '.and')             # \가 붙어서 .자체를 의미

. 모든 문자를 의미

re.search(r'p.g', 'pig')



^, $
^  문자열의 맨 앞부터 일치하는 경우 검색
$  문자열의 맨 뒤부터 일치하는 경우 검색

>>> print(re.search(r'b\w+a', 'cabana'))   # b로시작, 아무글자 1번이상, a로끝
>>> print(re.search(r'^b\w+a', 'cabana'))  # ^ 문자열 맨 앞부터 일치할 때
>>> print(re.search(r'^b\w+a', 'babana'))  # ^ 문자열 맨 앞부터 일치할 때
>>> print(re.search(r'b\w+a$', 'cabana'))  # $ 문자열 맨 마지막
>>> print(re.search(r'b\w+a$', 'cabanap'))
<re.Match object; span=(2, 6), match='bana'>
None
<re.Match object; span=(0, 6), match='babana'>
<re.Match object; span=(2, 6), match='bana'>
None


*, +, ?
패턴 뒤에 위치하는 *, +, ?는 해당 패턴이 반복적으로 존재하는지 검사
'+' : 1번 이상의 패턴이 발생 (1, 2 ~ )
'*' : 0번 이상의 패턴이 발생 (0, 1, 2 ~ )
'?' : 0 혹은 1번의 패턴이 발생 (있거나 없거나)
반복을 패턴의 경우 가능한 많은 부분이 매칭되도록 꼼꼼하게 검색하며, 가장 빨리 찾아진 것을 도출.
예를 들어 a[bcd]*b 패턴을 abcbdccb에서 검색하는 경우, 'ab, abcb, abcbdccb' 전부 가능 하지만 최대한 많은 부분이 매칭된 abcbdccb가 검색됨.

>>> print(re.search(r'a[bcd]*b', 'abcbdccb'))#a시작,bcd중하나0번이상반복,b로끝
>>> print(re.search(r'b\w+a', 'banana'))     #b시작,숫자나영자1번이상반복,a로끝
>>> print(re.search(r'i+', 'piigiii'))       #i가 반복되는 구간
>>> print(re.search(r'pi+g', 'piig'))        #p시작,i 1번이상반복,g로끝
>>> print(re.search(r'pi*g', 'piig'))        #p시작,i 0번이상반복,g로끝
>>> print(re.search(r'https?', 'http://www.naver.com')) # http로시작,s는 1번있거나없거나
<re.Match object; span=(0, 8), match='abcbdccb'>
<re.Match object; span=(0, 6), match='banana'>
<re.Match object; span=(1, 3), match='ii'>
<re.Match object; span=(0, 4), match='piig'>
<re.Match object; span=(0, 4), match='piig'>
<re.Match object; span=(0, 4), match='http'>


*?, +?
기본적으로 *, +, ?를 사용하면 greedy(맥시멈 매칭)하게 동작함
*?, +?을 이용하면 해당 기능을 non-greedy(미니멈 매칭)하게 구현

# <html>만 찾고 싶은데 <> 전체를 찾아서 <html>haha</html>
# ?는 최대가 아니라 최소로 찾음
>>> print(re.search(r'<.+>', '<html>haha</html>'))
>>> print(re.search(r'<.+?>', '<html>haha</html>'))
<re.Match object; span=(0, 17), match='<html>haha</html>'>
<re.Match object; span=(0, 6), match='<html>'>


{ }
*, +, ?을 사용하여 반복적인 패턴을 찾는 것이 가능하나, 반복의 횟수 제한은 불가
패턴뒤에 위치하는 중괄호{}에 숫자를 명시하면 해당 숫자 만큼의 반복인 경우에만 매칭

>>> print(re.search('pi{3}', 'piiig'))             # 3번 반복
>>> print(re.search('pi{3, 5}', 'piiiiiig'))       # 최소3~최대5번 반복
<re.Match object; span=(0, 4), match='piii'>
None



{ }?
{m,n}의 경우 m번에서 n번 반복하나 꼼꼼하게(greedy) 동작
{m,n}?로 사용하면 느슨하게(non-greedy) 동작. 즉, 최소 m번만 매칭하면 만족

>>> print(re.search(r'a{3,5}', 'aaaaa'))       # a 3-5번 검출 - 5개
>>> print(re.search(r'a{3,5}?', 'aaaaa'))      # 최소한만 검출 - 3개
<re.Match object; span=(0, 5), match='aaaaa'>
<re.Match object; span=(0, 3), match='aaa'>



관련 함수
search() : 문자열 전체를 검색하여 정규식과 매치되는지
'.search(찾고자하는 패턴, 찾을 대상)' 형식으로 특정 패턴 찾고자 할 때 사용
첫번째로 패턴을 찾으면 match 객체를 반환하고, 패턴을 찾지 못하면 None을 반환
search 함수는 여러가지 중에서 가장 먼저오는 것을 발견 (ex. 11, 12, 11, 19 중에서 11만 출력)

# .search(찾고자하는 패턴, 찾을 대상)
>>> m = re.search(r'abc', '123abcdef')       # abcdef에서'abc'있는지 찾음
>>> print(type(m))
>>> print(m.start())                         # 문자가검색된시작위치(시작포함)
>>> print(m.end())                           # 문자가검색된종료위치(종료미포함)
>>> print(m.group())                         # 해당 검색된 패턴 자체를 출력
<class 're.Match'>
3
6
abc

>>> print(re.search(r'\d\d\d', '112abcdef119'))   # 숫자 2개가 나란히 있는지
>>> print(re.search(r'\d\d\d\w', '112abcdef119')) # 숫자 3개, 문자 1개
>>> print(re.search(r'..\w\w', '@#$%ABCDabcd'))   # 어떠한 문자2개+문자2개
<re.Match object; span=(0, 3), match='112'>
<re.Match object; span=(0, 4), match='112a'>
<re.Match object; span=(2, 6), match='$%AB'>


match(): 문자열의 첫 시작부터 정규식과 매치되는지
search와 match 둘 다 검색 함수이나 match는 처음부터 맞는지 확인, search는 어디에 있어도 있으면 출력
시작부터 해당 패턴이 존재하지 않다면 None 반환

# .match(찾고자하는 패턴, 찾을 대상)
>>> print(re.match(r'\d\d\d', 'my number is 123'))    # 처음부터 안 나옴
>>> print(re.match(r'\d\d\d', '123 is my number'))
>>> print(re.search(r'^\d\d\d', '123 is my number'))  # ^문자열시작부터 탐색
None
<re.Match object; span=(0, 3), match='123'>
<re.Match object; span=(0, 3), match='123'>


findall() : 정규식과 매치되는 모든 문자열을 리스트로 반환
finditer() : 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 반환
search가 최초로 매칭되는 패턴만 반환한다면, findall은 매칭되는 전체의 패턴을 반환
매칭되는 모든 결과를 리스트 형태로 반환

>>> re.findall(r'[\w-]+@[\w.]+', \
           'test@gmail.com haha test2@gmail.com nice test test')
['test@gmail.com', 'test2@gmail.com']


sub
주어진 문자열에서 일치하는 모든 패턴을 찾아서 치환하고 그 결과를 문자열로 다시 반환
두번째 인자는 특정 문자열이 될 수도 있고, 함수가 될 수 도 있음
count가 0인 경우는 전체를, 1이상이면 해당 숫자만큼 치환 됨

# .sub(r'패턴', '치환','문자열')
>>> print(re.sub(r'[\w-]+@[\w.]+', 'great', \
>>>       'test@gmail.com haha test2@gmail.com nice test test'))
>>> print(re.sub(r'[\w-]+@[\w.]+', 'great', \
>>>       'test@gmail.com haha test2@gmail.com nice test test', \
>>>       count=1))
great haha great nice test test
great haha test2@gmail.com nice test test


compile
자주 사용하는 패턴을 compile로 해당표현식을 re.RegexObject 객체로 저장하여 사용
동일한 정규표현식을 매번 다시 쓰기 번거로움 해결

>>> email_reg = re.compile(r'[\w-]+@[\w.]+')

# 이미 패턴은 넘겨져 있으므로 문자열만 입력
>>> print(email_reg.search('test@gmail.com haha good'))
>>> print(email_reg.findall('test@gmail.com haha good'))
<re.Match object; span=(0, 14), match='test@gmail.com'>
['test@gmail.com']



※ 정규표현식 테스트하는 사이트

https://regex101.com/?ref=localhost


※ credit: Fast Campus, 머신러닝과 데이터분석 A-Z 올인원 패키지

https://docs.python.org/3/library/re.html

반응형