Python #33 | 정규 표현식_정규 표현식 II

신승호·2021년 3월 31일
0

Python

목록 보기
35/35
post-custom-banner

List

  • 정규 표현식
    • 정규 표현식 I
    • 정규 표현식 II

정규 표현식_정규 표현식 II


메타문자

메타문자의 종류

    • +, *, [], {}
    • 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 소비된다(변경된다)
  • |, ^, $, \A, \Z, \b, \B
    • zerowidth assertions
    • 매치가 진행될 때 현재 매치되고 있는 문자열의 위치가 소비되지 않는다.

|

  • | :or 과 동일한 의미
    • A | B = A 또는 B 라는 의미
    >>> p = re.compile('Crow|Servo')
    >>> m = p.match('CrowHello')
    >>> print(m)
    <re.Match object; span=(0, 4), match='Crow'>

^

  • ^ : 문자열의 맨 처음과 일치
    >>> print(re.search('^Life', 'Life is too short'))	# life가 문자열이 맨앞에 위치 했기 때문에 매치
    <re.Match object; span=(0, 4), match='Life'>
    >>> print(re.search('^Life', 'My Life'))	# life 문자열이 뒤에 위치했기 때문에 매치불가
    None

$

  • $ : 문자열의 맨 끝과 일치 (^ 와 반대)
    >>> print(re.search('short$', 'Life is too short'))	# short가 문자열이 맨뒤에 위치 했기 때문에 매치
    <re.Match object; span=(12, 17), match='short'>
    >>> print(re.search('short$', 'Life is too short, you need python'))	# short가 문자열 맨뒤에 위치 하지 않음
    None

\A

  • \A : 문자열의 처음과 매치 (^과 동일한 의미)

re.MULTILINE 옵션을 사용할 경우 달라짐

  • ^은 각 줄의 문자열의 처음과 매치
  • \A전체 문자열의 처음과 매치(줄 상관 없음)

\Z

  • \Z : 문자열의 끝과 매치 ($과 동일한 의미)

re.MULTILINE 옵션을 사용할 경우 달라짐

  • $은 각 줄의 문자열의 끝과 매치
  • \Z전체 문자열의 끝과 매치(줄 상관 없음)

\b

  • \b : 단어 구분자(Word boudnary)이며, whitespace로 구분한다
    • 입력된 문자 앞뒤에 whitespace가 있어야 한다.
>>> p = re.compile(r'\bclass\b')			# 압뒤가 whitespace로 구분된 class라는 단어와 매치
>>> print(p.search('no class at all'))			#그에따라 class와 매치
<re.Match object; span=(3, 8), match='class'>
>>> print(p.search('the declassified algorithm'))	# class 는 있지만 앞뒤 whitespace가 없기 때문에 매치 실패
None
>>> print(p.search('one subclass is'))			# class뒤에만 whitespace있고 앞에는 없기때문에 매치 실패
None

[\b 사용시 주의해야할 점]

  • \b 는 파이썬 리터럴 규칙에 의하면 백스페이스(BackSpace)를 의미
    • 단어 구분자라는걸 알려주기 위해 r'(raw string) 꼭 사용

\B

  • \B : 입력된 문자 앞뒤에 문자가 있어야 한다(whitespace 노 !)
    • \b와 반대
>>> print(p.search('no class at all'))  		# 앞뒤에 whitespace이 있음
None
>>> print(p.search('the declassified algorithm'))	# 앞뒤에 whitespace이 없기 때문에 매치	
<re.Match object; span=(6, 11), match='class'>
>>> print(p.search('one subclass is'))			# 뒤에 whitespace이 있음
None

그루핑

( )

  • 그루핑을 위해 메타문자 ( ) 를 사용한다.
    • (ABC)+

ABC 문자열이 계속해서 반복되는지 조사하는 정규식을 작성해보자 !

>>> p = re.compile('(ABC)+')			# ABC 가 1번 이상 반복하는것들 매칭 . !
>>> m = p.search('ABCABCABC OK?')		# 요기 있네 . .
>>> print(m)
<re.Match object; span=(0, 9), match='ABCABCABC'>
>>> print(m.group())
ABCABCABC

그루핑을 통해 원하는 값만 뽑아와 보자 !

>>> p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+")
>>> m = p.search("park 010-1234-1234")
  • \w+ = 이름

  • \s+ = " "(공백)

  • \d+ [-] \d+ [-] \d+ = 번호

  • 이름부분인 \w 에 그루핑을 해서 \w만 뽑아 오자

>>> p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")	# \w 그루핑 진행
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(1))				# group(인덱스)을 사용해서 출력
park

[group(인덱스)의 활용]

  • group(0) : 매치된 전체 문자열
  • group(1) : 첫 번째 그룹에 해당되는 문자열
  • group(2) : 두 번째 그룹에 해당되는 문자열
  • group(n) : n 번째 그룹에 해당되는 문자열
  • 전화번호 부분인 (\d+[-]\d+[-]\d+) 에 그루핑을 해서 뽑아 오자
>>> p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")	# 그루핑 완료
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(2))		# group(1) = (\w) / group(2) = (\d+[-]\d+[-]\d+)
010-1234-1234

그루핑된 문자열 재참조하기

  • 그루핑한 문자열은 재참조(Backreferences) 할수 있다.
    • (\b\w+)\s+\1 = (그룹) + " " + 그룹
      • 재참조 메타문자\1 첫 번째 그룹을 가리킨다
      • \2두 본째 그룹을 가리키겠지
    >>> p = re.compile(r'(\b\w+)\s+\1')
    >>> p.search('Paris in the the spring').group()
    'the the'

그루핑된 문자열에 이름 붙이기

  • 그룹에 인덱스 넘버가 아닌 이름(Named Group)을 뭍힐수 있다.
    • (?P<그룹명>...)
    • (\w+) -> (?P<name>\w+)
      (?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)	# \w 그루핑을 을 name으로 이름 지음
>>> p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group("name"))		# 인덱스가 아닌 그루핑의 이름인 name을 검색
park

이름이 붙여진 그루핑을 재참조하자

  • (?P=그룹이름) 사용
    • (?P<word>\b\w+)\s+(?P=word)
    • word 그루핑을 (?P=word) 로 재참조
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')	# word 그루핑을 재참조
>>> p.search('Paris in the the spring').group()
'the the'

전방 탐색

전방 탐색은 복잡한 정규식, 그루핌을 추가할 수 없는 정규식이 있을때 사용한다.

  • 긍정형 전방 탐색((?=...)) - ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
  • 부정형 전방 탐색((?!...)) - ... 에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다.*
>>> p = re.compile(".+:")
>>> m = p.search("http://google.com")
>>> print(m.group())
http:

긍정형 전방 탐색

  • (?=...)
    • 긍정형 전방 탐색은 ...부분이 검색에는 포함되지만 검색 결과에는 제외 된다.
    >>> p = re.compile(".+(?=:)")		# 기존 패턴 첫번째 줄 : 를 (?=:) 로 바꿨다
    >>> m = p.search("http://google.com")
    >>> print(m.group())
    http

전방 탐색 확용

  • 파일의 형식을 나타내는 정규식이다. 이때 foo.bar, autoexec.bat, sendmail.cf파일 중 .bat파일을 제외시켜보자.
    .*[.].*$ = 파일 이름 + . + 확장자
    • .*[.][^b].*$ - 불가
      • 확장자가 b로 시작하면 안된다라는 의미, 하지만 foo.bar도 걸러 냄
    • .*[.]([^b]..|.[^a].|..[^t])$ - 불가
      • | 사용, 확장자의 첫 번째 문자가 b가 아니거나 두 번째 문자가 a가 아니거나 세 번째 문자가 t가 아닌 경우를 의미
      • foo.bar는 제외되지 않고, autoexec.bat은 제외된다.
      • 하지만, 확장자의 문자가 2개인 sendmail.cf를 포함하지 못함..
    • .*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$ -가능
      • 메타문자 ? 는 바로 앞 문자가 있어도 되고 없어도되고 임.

여기서 .bat파일말고 .exe파일도 제외 해야한다면 ?

> 부정형 전방 탐색 사용 해보자 ! ! ! ! ! ! !

부정형 전방 탐색

  • (?!...)

    • 부정형 전방 탐색은 ...부분이 검색에 포함되지 않고 검색 결과에서도 제외 된다.
  • 위의 정규식을 쉽고 짧게 줄일 수 있다.

    • .*[.](?!bat$).*$ = .*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
    • bat가 아닌 경우에만 통과된다는 의미이며,
  • .exe 파일 또한 제외하라는 조건을 쉽게 추가 할 수 있다.

.*[.](?!bat$|exe$).*$

문자열 바꾸기

sub 메서드

  • sub(바꿀 문자열(replacement),바꿀 대상 문자열, count=#)

    • 정규식과 매치되는 부분다른 문자로 쉽게 바꿀 수 있다.
  • sub 메서드 사용

>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')		#"blue socks and red shoes"에서 "blue" 또는 "white" 또는 "red" 를 "colour" 바꾼다.
'colour socks and colour shoes'				# blue 와 red 가 colour로 바뀌었다 
  • sub 메서드 사용 바꾸기 횟수를 제한
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'				# count=1에 따라 1번만 실행

[subn 메서드]

  • sub메서드와 동일하지만 결괏값을 튜플로 돌려준다.
    • 튜플의 첫 번째 요소는 변경된 문자열이고, 두 번째 요소는 바꾸기가 발생한 횟수
    • (변경된 문자열, 바꾸기가 발생한 횟수)
      >> p = re.compile('(blue|white|red)')
      >> p.subn( 'colour', 'blue socks and red shoes')
      ('colour socks and colour shoes', 2)		# 바뀐 문자열, blue와 red가 바뀌었으니 2를 출력

sub 메서드 사용 시 참조 구문 사용하기

  • \g<그룹이름>
    • sub의 바꿀 문자열 부분에 정규식의 그룹 이름을 참조할 수 있게 된다.
    • 이를 통해 출력하는 문자열의 순서를 바꿀 수 있다.
>>> p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<phone> \g<name>", "park 010-1234-1234"))	# 기존 순서 name그룹 phone그룹 -> phone그룹 name 그룹 해줘라 
010-1234-1234 park 						# 기존 park 010-1234-1234 에서 출력된 값같이 바뀌었다.
>>> p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<2> \g<1>", "park 010-1234-1234"))		# 이름이 아닌 그루핑 번호를 써도 된다
010-1234-1234 park						# 동일한 값이 나오는걸 볼 수 있다.

sub 메서드의 매개변수로 함수 넣기

>>> def hexrepl(match):			# hexrepl 함수는 match 객체(위에서 숫자에 매치되는)를 입력으로 받아, 
        value = int(match.group())
        return hex(value)		# 16진수로 변환하여 돌려주는 함수
   
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')	# sub의 첫 번째 매개변수로 함수를 사용할 경우 사용된 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력 -> int()로 인해 'Call 65490 for printing, 49152 for user code.' 의 숫자 부분만 해당된다. 이 문자열의 숫자를 받아 16진법을 으로 돌려줌

매치된 match 객체가 입력
'Call 0xffd2 for printing, 0xc000 for user code.'

Greedy vs Non-Greedy

메타문자의 특성 Greedy

  • 메타 문자는 매우 탐욕스러워서 매치할 수 있는 최대한의 문자열을 모두 소비해 버린다.
>>> s = '<html><head><title>Title</title>'
>>> len(s)
32
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>

Non-Greedy 문자 "?"

  • ?
    • 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할
    • *?, +?, ??, {m,n}?
>>> print(re.match('<.*?>', s).group())
<html>
profile
신승홉니다
post-custom-banner

0개의 댓글