개발/Python

정규 표현식(Regular Expression)

Louisus 2020. 8. 7. 01:02
728x90
  • 정규 표현

    • 특정한 패턴과 일치하는 문자열을 '검색', '치환', '제거' 하는 기능 지원
    • 정규 표현식의 도움 없이 패턴을 찾는 작업은 불완전 하거나, 작업의 비용이 큼
    • 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)))