파이썬 정규표현식

GGob2._.·2024년 2월 16일
0

정규표현식

파이썬의 정규표현식에 대해 알아보자.


메타 문자

메타 문자란, 정규표현식에서 사용했을 때, 원래의 의미가 아닌 특별한 의미로 사용되는 아래 문자를 의미한다.

. ^ $ * + ? { } [ ] \ | ( )

  1. [ ] 문자 - 문자 클래스

문자 클래스로 만들어진 정규식은 [] 사이의 문자들과 매치 라는 의미를 갖는다.

예를 들어, 표현식이 [abc]인 경우, a b c 중 한 개의 문자와 매치를 의미한다.

  • a: [abc]a가 존재하므로, 매치된다.
  • before: [abc]b가 존재하므로, 매치된다.
  • dude: [abc] 중 어느 하나도 포함하고 있지 않으므로, 매치되지 않는다.

[] 안에서 두 문자 사이에 -을 사용하면, 두 문자 사이의 범위를 의미한다.

예를 들어, 아래와 같다.

  • [a-zA-Z] : 모든 알파벳
  • [0-9] : 모든 숫자

문자 클래스 [] 안에는 어떤 문자나 메타 문자를 사용할 수 있지만, ^을 사용할 때, 주의해야 한다.
^은 반대(not)의 의미를 가져, [^0-9]라는 정규 표현식은 숫자가 아닌 문자만 매치된다.


  1. . (dot) 문자 - \n을 제외한 모든 문자

정규 표현식의 . 메타 문자는 줄바꿈 문자인 \n을 제외한 모든 문자와 매치되는 것을 의미한다.

a.b => "a + 모든 문자 + b" , a와 b 문자 사이에 어떤 문자가 들어가도 모두 매치된다는 뜻

  • aab : 가운데 존재하는 'a'가 모든 문자를 의미하는 .과 일치하므로, 정규식과 매치
  • a0b : 가운데 존재하는 '0'이 모든 문자를 의미하는 .과 일치하므로, 정규식과 매치
  • abc : ab 사이에 어떤 문자라도 존재하지 않기 때문에, 매치되지 않음

하지만, a[.]b의 경우는 위와 다르게, 문자 그대로를 의미한다.

"a + . + b"


  1. * 문자

* 문자는 반복을 의미한다. * 바로 앞에 있는 문자 a가 0부터 무한대까지 반복될 수 있다는 의미로 사용한다.

ca*t

  • ct : "a"가 0번 반복되어 매치
  • cat : "a"가 1번 반복되어 매치
  • caat : "a"가 2번 반복되어 매치

  1. + 문자

* 문자가 최소 0번부터 사용할 수 있는거라면, +는 반복 횟수가 1부터 시작한다.

ca+t

  • ct : "a"가 0번 반복되어 매치되지 않음
  • cat : "a"가 1번 반복되어 매치
  • caat : "a"가 2번 반복되어 매치

  1. { }, ? 문자

반복횟수를 제한하고 싶을 때 { }을 사용해 고정할 수 있다. {m, n} 정규식을 사용하면, 반복횟수가 m부터 n까지인 문자와 매치할 수 있다.

만약, {3,}처럼 사용하면 반복 횟수가 3 이상인 경우이고, {, 3}처럼 사용하면 반복 횟수가 3이하인 경우를 의미한다.

생략된 m은 0과 동일하며, 생략된 n은 무한대를 의미한다. (실제로는 약 2억개)

{1, }은 +와 같고, {0, }은 *와 동일하다.

  • {m} -> ca{2}t -> "c + a를 반드시 2번 반복 + t"
  • {m, n} -> ca{2, 5}t -> "c + a를 2~5회 반복 + t"

반복은 아니지만, ? 문자는 {0, 1}을 의미한다.

  • ab?c -> "a + b가 있어도 되고, 없어도 된다 + c" => abc, ac 둘 다 가능

파이썬 re 모듈

파이썬은 re(regular expression) 모델을 제공한다. 파이썬을 설치할 때 자동으로 설치되는 표준 라이브러리로, 다음과 같이 사용한다.

import re
p = re.compile('ab*') # re.compile을 사용하여, 정규표현식을 컴파일한다. 

  1. 문자열 검색

컴파일된 패턴 객체를 사용해 문자열 검색을 수행할 수 있다.

  • match() : 문자열의 처음부터 정규식과 매치되는지 조사한다.
  • search() : 문자열 전체를 검색하여 정규식과 매치되는지 조사한다.
  • findall() : 정규식과 매치되는 모든 문자열(substring)을 리스트로 리턴한다.
  • finditer() : 정규식과 매치되는 모든 문자열(substring)을 반복 가능한 객체로 리턴한다.\

match(), search()는 정규식과 매치될 때 match 객체를 리턴하고, 매치되지 않을 때 None을 리턴한다.


match()

import re
p = re.compile('[a-z]+')

m = p.match("python")
print(m)

>>> <re.Match object; span=(0, 6), match='python'>  # python은 정규식에 부합

m = p.match("3 python")								# 3이 부합하지 않아 None 리턴 
print(m)
None

match()의 결과로 match 객체 또는 None을 리턴하기 때문에, 보통 다음과 같은 흐름으로 작성한다.

p = re.compile(정규표현식)
m = p.match('조사할 문자열')

if m: 	# match의 결과값이 존재하는 경우에만 다음 작업 수행
    print('Match found: ', m.group())
else:
    print('No match')

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'>

python 문자열에 search() 메서드를 수행하면, match() 메서드를 수행했을 때와 동일하게 매치된다.

'3 python' 문자열의 첫 번째 문자는 '3' 이지만, 'search'는 문자열의 처음부터 검색하는 것이 아니라 문자열 전체를 검색하기 때문에, '3' 이후의 'python' 문자열과 매치된다.


findall()

result = p.findall("life is too short")
print(result)
['life', 'is', 'too', 'short']

findall()은 패턴 [a-z]+과 매치되는 모든 값을 찾아 리스트로 리턴한다.


finditer

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'>

finditerfindall과 동일하지만, 그 결과로 반복 가능한 객체 (iterator object)를 리턴한다. 반복 가능한 객체가 포함하는 각각의 요소는 match는 객체다.


match 객체의 메서드

match 객체란 앞에서 살펴본 p.match, p.search, p.finditer 메서드에 의해 리턴된 매치 객체(Match Object)를 의미한다.

  • group : 매치된 문자열을 리턴한다.
  • start : 매치된 문자열의 시작 위치를 리턴한다.
  • end : 매치된 문자열의 끝 위치를 리턴한다.
  • span : 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 리턴한다.
m = p.match("python")

m.group()
'python'

m.start()
0

m.end()
6

m.span()
(0, 6)

match 메서드를 수행한 결과로 리턴된 match 객체로 start 메서드를 사용했을 때, 결과값은 항상 0일 수 밖에 없다. match 메서드는 항상 문자열의 시작부터 조사하기 때문이다.

만약 search 메서드를 사용했다면, 다른 값이 나올 수 있다.

m = p.search("3 python")

m.group()
'python'

m.start()
2

m.end()
8

m.span()
(2, 8)

컴파일 옵션

  • DOTALL(S): .(dot)이 줄바꿈 문자를 포함해 모든 문자와 매치될 수 있게 한다.
  • IGNORECASE(I): 대소문자에 관계없이 매치될 수 있게 한다.
  • MULTILINE(M): 여러 줄과 매치될 수 있게 한다. ^, $ 메타 문자 사용과 관계 있는 옵션이다.
  • VERBOSE(X): verbose 모드를 사용할 수 있게 한다. 정규식을 보기 편하게 만들 수 있고 주석 등을 사용할 수 있게 된다.

옵션을 사용할 때는 re.DOTALL 처럼 전체 옵션 이름을 써도 되고, re.S처럼 약어를 써도 된다.


DOTALL, S

. 메타 문자는 줄바꿈 문자 \n를 제외한 모든 문자와 매치되는 규칙이 있다. 만약 \n 문자도 포함하여 매치하고 싶다면, re.DOTALL, re.S 옵션을 사용해 정규식을 컴파일하면 된다.

import re
p = re.compile('a.b')
m = p.match('a\nb')

print(m)
None

정규식이 a.b인 경우, 문자열 a\nb는 매치되지 않는다는 것을 알 수 있다. \n.메타 문자와 매치되지 않기 때문이다. \n 문자와도 매치되게 하려면 다음과 같이 re.DOTALL 옵션을 사용해야 한다.

p = re.compile('a.b', re.DOTALL)
m = p.match('a\nb')
print(m)

<re.Match object; span=(0, 3), match='a\nb'>

re.DOTALL 옵션은 여러 줄로 이루어진 문자열에서 줄바꿈 문자에 상관없이 검색할 때 많이 사용한다.


IGNORECASE, I

re.IGNORECASE 또는 re.I 옵션은 대소문자 구별 없이 매치를 수행할 때 사용하는 옵션이다.

p = re.compile('[a-z]+', re.I)
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'>

[a-z]+ 정규식은 소문자만을 의미하지만, re.I 옵션으로 대소문자 구별 없이 매치된다.


MULTILINE, M

re.MULTILINE, re.M 옵션은 ^, $와 연관된 옵션이다.

^는 문자열의 처음, $는 문자열의 마지막을 의미한다.

예를 들어, ^python인 경우, 문자열의 처음은 항상 python으로 시작해야 매치하고, python$이라면, 문자열의 마지막은 항상 python으로 끝나야 매치된다.

# multiline.py
import re
p = re.compile("^python\s\w+")

data = """python one
life is too short
python two
you need python
python three"""

print(p.findall(data))

정규식 ^python\s\w+python이라는 문자열로 시작하고, 그 뒤에 화이트스페이스, 그 뒤에 단어가 와야 한다는 의미다.

해당 스크립트를 실행하면, 다음과 같은 결과를 리턴한다.

['python one']

^ 메타 문자에 의해 python이라는 문자열을 사용한 첫 번째 줄만 매치된 것이다.

하지만 ^ 메타 문제를 문자열 전체의 처음이 아니라, 각 라인의 처음으로 인식시키고 싶은 경우에는 re.MULTILINE, re.M을 활용한다.

# multiline.py
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))

re.MULTILINE 옵션으로 인해 ^ 메타 문자가 문자열 전체가 아닌 각 줄의 처음이라는 의미를 가지게 되었다.

['python one', 'python two', 'python three']

즉, re.MULTILINE 옵션은 ^, $ 메타 문자를 문자열의 각 줄마다 적용해 주는 것이다.


VERBOSE, X

charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);')

어려운 정규식을 주석 단위, 줄 단위로 구분하기 위해 re.VERVOSE, re.X 옵션을 사용한다.

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)

re.VERBOSE 옵션을 사용하면, 문자열에 사용된 화이트스페이스는 컴파일할 때 제거된다.


역슬래시 문제

정규표현식을 파이썬에서 사용할 때 혼란을 주는 요소가 1가지 존재하는데, 바로 역슬래시(\)다.

어떤 파일안에 있는 \section 문자열을 찾기 위한 정규식을 만든다고 가정해본다.

\section 이 정규식은 \s 문자가 whitespace로 해석되어 의도한 대로 매치가 이루어지지 않는다.

이 표현은 다음과 동일한 의미로 사용할 수 있다.

[ \t\n\r\f\v]ection

의도한대로 매치하고 싶다면, 다음과 같이 변경해야 한다.

\\section

즉, 앞 정규식에서 사용한 \ 문자가 문자열 자체라는 것을 알려주기 위해 역슬래시 2개를 사용해 이스케이프 처리를 해야 한다.

해당 정규식을 컴파일하려면 다음과 같이 작성해야 한다.

p = re.compile('\\section')

이처럼 정규식을 만들어서 컴파일하면, 실제 파이썬 정규식 엔진에는 파이썬 문자열 리터럴 규칙에 따라, \\\로 변경되어 \section이 전달된다.

결국, 아래와 같이 역슬래시 4개를 사용해야 하는 상황이 발생한다.

p = re.compile('\\\\section')

이를 해결하기 위해 raw string을 사용한다.

p = re.compile(r'\\section')

이와 같이 정규식 문자열 앞에 r 문자를 삽입하면, 이 정규식은 raw string 규칙에 의해 역슬래시 2개 대신 1개만 써도 2개를 쓴 것과 동일한 의미를 가지게 된다.


내용 참고

profile
소통을 잘하는 개발자가 되고 싶습니다.

0개의 댓글