Data/Python

[Python] 파이썬 정규표현식 - 검색, 옵션

재은초 2023. 6. 18. 11:31
반응형

정규 표현식(Regular Expressions)이란?

  • 정규식은 복잡한 문자열을 처리할 때 사용하는 기법으로, 파이썬만의 고유 문법이 아니라 문자열을 처리하는 모든 곳에서 사용한다.

 

정규 표현식을 지원하는 re 모듈

>>> import re
>>> p = re.compile('[a-z]+')
>>> m = p.match("python")

# 축약 가능
>>> m = re.match('[a-z]+', "python")     # 컴파일 match메서드 한번에 수행 가능


정규식을 이용한 문자열 검색

  • match(): 문자열 처음부터 정규식과 매치되는지 조사
#  매치될 때는 match 객체를 돌려주고, 매치되지 않을 때는 None
>>> m = p.match("python")
>>> print(m)
<re.Match object; span=(0, 6), match='python'>

>>> m = p.match("3 python")
>>> print(m)
None
  • search():문자열 전체를 검색해 정규식과 매치되는지 조사
>>> m = p.search("python")
>>> print(m)
<re.Match object; span=(0, 6), match='python'>

>>> m = p.search("3 python")
>>> print(m)
<re.Match object; span=(2, 8), match='python'>
  • findall(): 정규식과 매치되는 모든 문자열을 리스트로 돌려줌
>>> result = p.findall("life is too short")
>>> print(result)
['life', 'is', 'too', 'short']       # 정규식과 매치해서 리스트로 돌려줌
  • finditer(): 정규식과 매치되는 모든 문자열을 반복 가능한 객체로 돌려줌
  • findall과 finditer는 동일하지만 그 결과로 findall은 리스트 finditer은 반복 가능한 match 객체를 돌려준다.
>>> result = p.finditer("life is too short")
>>> print(result)
<callable_iterator object at 0x01F5E390>

>>> for r in result: print(r)
...
<re.Match object; span=(0, 4), match='life'>
<re.Match object; span=(5, 7), match='is'>
<re.Match object; span=(8, 11), match='too'>
<re.Match object; span=(12, 17), match='short'>

 

Match 객체의 메서드

  • match와 search 메서드를 수행한 결과로 돌려준 match 객체
>>> m = p.match("python")
>>> m.group()              # group() 매치된 문자열 반환
'python'
>>> m.start()              # start() 매치된 문자열의 시작 위치 반환
0                          # match 메서드는 항상 시작부터 조사하므로 항상 0
>>> m.end()                # end() 매치된 문자열의 끝 위치 반환
6
>>> m.span()               # span() 매치된 문자열의 (시작, 끝) 튜플 반환
(0, 6)

>>> m = p.search("3 python")
>>> m.group()
'python'
>>> m.start()              # start 메서드는 문자열 전체를 조사
2
>>> m.end()
8
>>> m.span()
(2, 8)


컴파일 옵션

DOTALL(S)

  • 메타문자 . 이 줄바꿈 문자를 포함하여 모든 문자와 매치할 수 있도록 한다.
    보통 여러 줄로 이루어진 문자열에서 \n에 상관없이 검색할 때 많이 사용한다.
# 메타문자 . 이 원래 줄바꿈 문자 \n를 제외한 모든 문자와 매치
>>> import re
>>> p = re.compile('a.b')
>>> m = p.match('a\nb')        
>>> print(m)                 
None

# \n 도 포함하여 모든 문자와 매치
>>> p = re.compile('a.b', re.DOTALL)         # re.S 약어도 가능
>>> m = p.match('a\nb')
>>> print(m)
<re.Match object; span=(0, 3), match='a\nb'>

IGNORECASE(I)

  • 대소문자에 관계없이 매치할 수 있도록 한다.
>>> p = re.compile('[a-z]+', re.I)            # re.IGNORECASE와 동일
>>> p.match('python')
<re.Match object; span=(0, 6), match='python'>

>>> p.match('Python')
<re.Match object; span=(0, 6), match='Python'>

>>> p.match('PYTHON')
<re.Match object; span=(0, 6), match='PYTHON'>

MULTILINE(M)

  • 여러줄과 매치할 수 있도록 한다. 
  • ^은 문자열 처음, $ 은 문자열의 마지막을 의미
  • ^python인 경우 문자열의 처음은 항상 python으로 시작해야 매치되고, 만약 정규식이 python$이라면 문자열의 마지막은 항상 python으로 끝나야 매치된다
# 원래 한 줄만 매치
>>> import re
>>> p = re.compile("^python\s\w+")   # python 문자열로 시작 + 여백 + 단어
...
>>> data = """python one
>>> life is too short
>>> python two
>>> you need python
>>> python three"""
...
>>> print(p.findall(data))           
['python one']                        # ^ 때문에 python 사용한 첫줄만 매치

# Multiline 옵션으로 여러줄 매치 - ^가 문자열 전체가 아닌 각 줄의 처음 의미
>>> import re
>>> p = re.compile("^python\s\w+", re.MULTILINE)
...
>>> data = """python one
>>> life is too short
>>> python two
>>> you need python
>>> python three"""
...
>>> print(p.findall(data))
['python one', 'python two', 'python three']

VERBOSE(X)

  • verbose 모드를 사용하면 문자열에 사용된 여백들이 컴파일할 때 제거되며, 줄 단위로 #기호를 사용하여 주석문을 작성할 수 있다. (단 [ ] 안에 사용한 여백는 제외)
# 원래 복잡
>>> charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')

# verbose모드 - 여러줄 + 주석
charref = re.compile(r"""
 &[#]                # Start of a numeric entity reference
 (
     0[0-7]+         # Octal form
   | [0-9]+          # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                   # Trailing semicolon
""", re.VERBOSE)

\ 백슬래시

  • \ 문자가 문자열 자체임을 알려 주기 위해 백슬래시 4개를 사용하여 이스케이프 처리를 해야 하므로 간단하게 Raw string 처리 해줌
# 백슬래시를 문자열 취급하려면 \\\\ 백슬래시 4번 써줌
>>> p = re.compile('\\\\section')   # \s가 여백으로 해석되므로

# 복잡하므로 raw string 처리
>>> p = re.compile(r'\\section')

 

전방 탐색 확장 구문

긍정형 전방 탐색((?=...))

  •  ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다 - 검색에는 포함되지만 검색 결과에는 제외됨.
# 원래
>>> 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

부정형 전방 탐색((?!...))

  • ...에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
# 원래
.*[.].*$                 # 파일 이름 + . + 확장자

# 특정 확장자 파일 제외
.*[.](?!bat$).*$         # 확장자 bat 아닌 경우에만 통과
.*[.](?!bat$|exe$).*$    # 확장자 bat, exe 아닌 경우에만 통과


매치되는 문자열 바꾸기

sub 메서드(바꿀 문자열, 대상 문자열)

# sub메서드로 바꾸기
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'

# 바꾸기 횟수 제한
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'
  • sub의 바꿀 문자열 부분에 \g<그룹이름>이나 \g<참조번호> 를 사용하면 정규식의 그룹 이름을 참조할 수 있게 된다.
# 이름 전화번호 순서 바꾸기
>>> p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<phone> \g<1>", "park 010-1234-1234"))
010-1234-1234 park
  • sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력된다. 그리고 매치되는 문자열은 함수의 반환 값으로 바뀌게 된다.
# sub 메서드의 첫번째 매개변수로 함수도 가능
>>> def hexrepl(match):             # 16진수로 변환 함수
...     value = int(match.group())
...     return hex(value)
...
>>> p = re.compile(r'\d+')
>>> p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.')
'Call 0xffd2 for printing, 0xc000 for user code.'

subn 메서드(바꿀 문자열, 대상 문자열)

  • sub와 동일한 기능을 하지만 반환 결과를 튜플로 돌려줌.
  • 돌려준 튜플의 첫 번째 요소는 변경된 문자열이고 두 번째 요소는 바꾸기가 발생한 횟수
>>> p = re.compile('(blue|white|red)')
>>> p.subn( 'colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)

 

Greedy과 Non-Greedy

  • 보통 메타 문자는 매우 탐욕(Greedy)스러워서 매치할 수 있는 최대한의 문자열을 모두 소비
  • Non-greedy 문자인 ?는 *?, +?, ??, {m,n}?와 같이 사용하는데, 가능한 최소한의 반복을 수행하도록 도와주는 역할.
>>> s = '<html><head><title>Title</title>'

# Greedy - 가능한 최대로 매치
>>> print(re.match('<.*>', s).span())
(0, 32)
>>> print(re.match('<.*>', s).group())
<html><head><title>Title</title>

# Non-greedy - 가능한 최소한으로 매치
>>> print(re.match('<.*?>', s).group())
<html>

 

Reference

반응형