Wisdom’s Cloud

[PYTHON] 14. 정규 표현식 본문

PYTHON

[PYTHON] 14. 정규 표현식

지혜로운지혜쓰 2021. 3. 3. 15:55
정규 표현식
  • 정규 표현식(Regular Expressions)은 복잡한 문자열을 처리할 때 사용하는 기법으로, 파이썬만의 고유 문법이 아니라 문자열을 처리하는 모든 곳에서 사용한다.

  • 따라서 정규 표현식을 사용하면 코드가 상당히 간결해지며, 만약 찾으려는 문자열 또는 바꾸어야 할 문자열의 규칙이 매우 복잡하다면 정규식의 효용은 더 커지게 된다.

 

 

(소비되는) 메타 문자
  • 정규 표현식에서 사용하는 메타 문자(meta characters)에는 다음과 같은 것들이 있으며, 메타 문자란 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자를 말한다.

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

 

  • 문자 클래스 [ ]: 문자 클래스로 만들어진 정규식은 '[ ]  사이의 문자들과 매치'라는 의미를 갖는다. 즉 정규 표현식인 [abc]라면 이 표현식의 의미는 'a, b, c 중 한 개의 문자와 매치'를 뜻한다. 또한 [ ] 안의 두 문자 사이에는 하이픈(-)을 사용하면 두 문자 사이의 범위(From - To)를 의미한다. 예를 들어 [a-c]라는 정규 표현식은 [abc]와 동일하고 [0-5]는 [012345]와 동일하다. 문자 클래스 안에는 어떤 문자나 메타 문자도 사용할 수 있지만 주의해야 할 메타 문자 한 가지가 있다. 그것은 바로 ^인데, 문자 클래스 안에 ^ 메타 문자를 사용할 경우에는 반대(not)이라는 의미를 갖는다. 예를 들어 [^0-9]라는 정규 표현식은 숫자가 아닌 문자만 매치된다.

정규식 문자열 매치 여부 설명
[abc] a Yes "a"는 정규식과 일치하는 문자인 "a"가 있으므로 매치
before Yes "before"는 정규식과 일치하는 문자인 "b"가 있으므로 매치
dude No "dude"는 정규식과 일치하는 문자인 a, b, c 중 어느 하나도 포함하고 있지 않으므로 매치되지 않음

 

  • Dot(.): Dot 메타 문자는 줄바꿈 문자인 \n을 제외한 모든 문자와 매치됨을 의미한다. 

정규식 문자열 매치 여부 설명
a.b aab Yes "aab"는 가운데 문자 "a"가 모든 문자를 의미하는 .과 일치하므로 정규식과 매치
a0b Yes "a0b"는 가운데 문자 "0"이 모든 문자를 의미하는 .과 일치하므로 정규식과 매치
abc No "abc"는 "a" 문자와 "b" 문자 사이에 어떤 문자라도 하나는 있어야 하는 이 정규식과 일치하지 않으므로 매치되지 않음

 

  • 반복(*): *은 * 바로 앞에 있는 문자가 0부터 무한대로 반복될 수 있다는 의미다.

정규식 문자열 매치 여부 설명
ca*t ct Yes "a"가 0번 반복되어 매치
cat Yes "a"가 0번 이상 반복되어 매치(1번 반복)
caaat Yes "a"가 0번 이상 반복되어 매치(3번 반복)

 

  • 반복(+): +는 최소 1번 이상 반복될 때 사용한다. 즉 *가 반복 횟수 0부터라면 +는 반복 회수 1부터인 것이다.

정규식 문자열 매치 여부 설명
ca+t ct No "a"가 0번 반복되어 매치되지 않음
cat Yes "a"가 1번 이상 반복되어 매치(1번 반복)
caaat Yes "a"가 1번 이상 반복되어 매치(3번 반복)

 

  • 반복({m, n}, ?): {m, n} 정규식을 사용하면 반복 회수가 m부터 n까지 매치할 수 있으며, m 또는 n을 생략할 수도 있다. 만약 {3,}처럼 사용하면 반복 횟수가 3이상인 경우이고, {,3}처럼 사용하면 반복 횟수가 3이하를 의미한다. 즉 생략된 m은 0과 동일하며, 생략된 n은 무한대(2억 개 미만)의 의미를 갖는다. 또한 반복은 아니지만 이와 비슷한 개념으로 ?이 있는데, ? 메타 문자가 의미하는 것은 {0, 1}이다.

정규식 문자열 매치 여부 설명
ca{2}t cat No "a"가 1번만 반복되어 매치되지 않음
caat Yes "a"가 2번 반복되어 매치
ca{2,5}t cat No "a"가 1번만 반복되어 매치되지 않음
caat Yes "a"가 2번 반복되어 매치
caaaaat Yes "a"가 5번 반복되어 매치
ab?c abc Yes "b"가 1번 사용되어 매치
ac Yes "b"가 0번 사용되어 매치

 

 

파이썬에서 정규 표현식을 지원하는 re 모듈
  • 파이썬은 정규 표현식을 지원하기 위해 re 모듈을 제공하며, re 모듈은 파이썬을 설치할 때 자동으로 설치되는 기본 라이브러리로 사용 방법은 다음과 같다.

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

 

 

정규식을 사용한 문자열 검색
  • match(): match 메서드는 문자열의 처음부터 정규식과 매치되는지 조사한다.

>>> import re
>>> p = re.compile('[a-z]+')
>>> m = p.match("python")
>>> print(m)
<re.Match object; span=(0, 6), match='python'>
>>> m = p.match("3 python")
>>> print(m)
None
# "python" 문자열은 [a-z]+ 정규식에 부합되므로 match 객체를 돌려준다.
# "3 python" 문자열은 처음에 나오는 문자 3이 정규식 [a-z]+에 부합되지 않으므로 None을 돌려준다.

 

  • search(): 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'>
# "python" 문자열에 search 메서드를 수행하면 match 메서드를 수행했을 때와 동일하게 매치된다.
# "3 python" 문자열의 첫 번째 문자는 "3"이지만 search 는 문자열의 처음부터 검색하는 것이 아니라
# 문자열 전체를 검색하기 때문에 "3 " 이후의 "python" 문자열과 매치된다.

 

  • findall(): findall 메서드는 정규식과 매치되는 모든 문자열을 리스트로 돌려준다.

>>> result = p.findall("life is too short")
>>> print(result)
['life', 'is', 'too', 'short']
# "life is too short" 문자열의 'life', 'is', 'too', 'short' 단어를 각각 [a-z] 정규식과 매치해서 리스트로 돌려준다.

 

  • finditer(): 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'>
# finditer는 findall과 동일하지만 그 결과로 반복 가능한 객체를 돌려주며, 반복 가능한 객체가 포함하는 각각의 요소는 match 객체이다.

 

 

match 객체의 메서드
메서드 목적
group() 매치된 문자열을 돌려준다.
start() 매치된 문자열의 시작 위치를 돌려준다.
end() 매치된 문자열의 끝 위치를 돌려준다.
span() 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다.
>>> import re
>>> p = re.compile('[a-z]+')
>>> m = p.match("python")
>>> m.group()
'python'
>>> m.start()
0
>>> m.end()
6
>>> m.span()
(0, 6)
>>> 
>>> m = p.search("3 python")
>>> m.group()
'python'
>>> m.start()
2
>>> m.end()
8
>>> m.span()
(2, 8)

 

 

컴파일 옵션
  • DOTALL, S: dot 문자(.)가 줄바꿈 문자를 포함하여 모든 문자와 매치한다.

>>> import re
>>> p = re.compile('a.b', re.DOTALL)
>>> m = p.match('a\nb')
>>> print(m)
<re.Match object; span=(0, 3), match='a\nb'>
# . 메타 문자는 줄바꿈 문자(\n)를 제외한 모든 문자와 매치되는 규칙이 있다.
# 만약 \n 문자도 포함하여 매치하고 싶다면 re.DOTALL 또는 re.S 옵션을 사용해 정규식을 컴파일하면 된다.

 

  • IGNORECASE, I: 대소문자에 관계 없이 매치한다.

>>> p = re.compile('[a-z]', re.I)
>>> p.match('python')
<re.Match object; span=(0, 1), match='p'>
>>> p.match('Python')
<re.Match object; span=(0, 1), match='P'>
>>> p.match('PYTHON')
<re.Match object; span=(0, 1), match='P'>
# [a-z] 정규식은 소문자만을 의미하지만 re.I 옵션은 대소문자 구별 없이 매치된다.

 

  • MULTILINE, M: 여러 줄과 매치한다. ^와 $ 메타문자의 사용과 관계가 있는 옵션이다.

>>> 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']
# ^는 문자열의 처음을 의미하고, $는 문자열의 마지막을 의미한다.
# 따라서 정규식 '^python\s\w+'은 python이라는 문자열로 시작하고, 그 뒤에 whitespace, 그 뒤에 단어가 와야 한다는 의미이다.
# ^ 메타 문자를 문자열 전체의 처음이 아니라 각 라인의 처음으로 인식시키고 싶은 경우에는 다음과 같이 re.MULTILINE 또는 re.M 옵션을 사용하면 된다.

 

  • VERBOSE, X: 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)
# 이해하기 어려운 정규식을 주석 또는 줄 단위로 구분하기 위해 다음과 같이 re.VERBOSE 또는 re.X 옵션을 사용한다.
# 또한 re.VERBOSE 옵션을 사용하면 문자열에 사용된 whitespace는 컴파일할 때 제거되며, 줄 단위로 # 기호를 사용하여 주석문을 작성할 수 있다.

 

 

백슬래시 문제
# 정규 표현식을 파이썬에서 사용할 때 혼란을 주는 요소가 한 가지 있는데, 바로 백슬래시(\)이다.
# 예를 들어 어떤 파일 안에 있는 "\section" 문자열을 찾기 위한 정규식을 만든다고 가정해 보자.
\section

# 이 정규식은 \s 문자가 whitespace로 해석되어 의도한 대로 매치가 이루어지지 않는다.
# \s 문자가 이스케이프 코드 \t, \n, \r, \f, \v로 해석되기 때문에 위 표현은 다음과 동일한 의미가 된다.
[ \t\n\r\f\v]ection]

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

# 즉 위 정규식에서 사용한 \ 문자가 문자열 자체임을 알려주기 위해 백슬래시 2개를 사용하여 이스케이프 처리를 해야 한다.
# 따라서 위 정규식을 컴파일하려면 다음과 같이 작성해야 한다.
>>> p = re.compile('\\section')

# 그런데 여기에서 또 하나의 문제가 발견된다.
# 위처럼 정규식을 만들어서 컴파일하면 실제 파이썬 정규식 엔진에는 파이썬 문자열 리터럴 규칙에 따라 \\이 \로 변경되어 \section이 전달된다.
# 결국 정규식 엔진에 \\ 문자를 전달하려면 파이썬은 \\\\처럼 백슬래시를 4개나 사용해야 한다.
>>> p = re.compile('\\\\section')

# 이렇게 해야만 원하는 결과를 얻을 수 있다.
# 만약 위와 같이 \를 사용한 표현이 계속 반복되는 정규식이라면 너무 복잡해서 이해하기 쉽지 않을 것이다.
# 이러한 문제로 인해 파이썬 정규식에는 Raw String 규칙이 생겨나게 되었다.
# 즉 컴파일해야 하는 정규식이 Raw String임을 알려 줄 수 있도록 파이썬 문법을 만든 것이며, 그 방법은 다음과 같다.
>>> p = re.compile(r'\\section')

# 위와 같이 정규식 문자열 앞에 r 문자를 삽입하면 이 정규식은 Raw String 규칙에 의하여 백슬래시 2개 대신 1개만 써도 2개를 쓴 것과 동일한 의미를 갖게 된다.

 

 

(소비가 없는) 메타 문자
  • |: | 메타 문자는 or과 동일한 의미로 사용된다. A|B라는 정규식이 있다면 A 또는 B라는 의미가 된다.

>>> p = re.compile('Crow|Servo')
>>> m = p.match('CrowHello')
>>> print(m)
<re.Match object; span=(0, 4), match='Crow'>

 

  • ^: ^ 메타 문자는 문자열의 맨 처음과 일치함을 의미한다. 앞에서 살펴본 컴파일 옵션 re.MULTILINE을 사용할 경우에는 여러 줄의 문자열일 때 각 줄의 처음과 일치하게 된다.

>>> print(re.search('^Life', 'Life is too short'))
<re.Match object; span=(0, 4), match='Life'>
>>> print(re.search('^Life', 'My Life'))
None

 

  • $: $ 메타 문자는 ^ 메타 문자와 반대의 경우이다. 즉 $는 문자열의 맨 끝과 매치함을 의미한다.

>>> print(re.search('short$', 'Life is too short'))
<re.Match object; span=(12, 17), match='short'>
>>> print(re.search('short$', 'Life is too short, you need python'))
None

 

  • \A: \A는 문자열의 처음과 매치됨을 의미한다. ^ 메다 문자와 동일한 의미이지만 re.MULTILINE 옵션을 사용할 경우에는 다르게 해석된다. re.MULTILINE 옵션을 사용할 경우 ^는 각 줄의 문자열의 처음과 매치되지만 \A는 줄과 상관없이 전체 문자열의 처음하고만 매치된다.

 

  • \Z: \Z는 문자열의 끝과 매치됨을 의미한다. 이것 역시 \A와 동일하게 re.MULTILINE 옵션을 사용할 경우 $ 메타 문자와는 달리 전체 문자열의 끝과 매치된다.

 

  • \b: \b는 단어 구분자(Word Boundary)이다. 보통 단어는 whitespace에 의해 구분된다. \b는 파이썬 리터럴 규칙에 의하면 백스페이스를 의미하므로, 백스페이스가 아닌 단어 구분자임을 알려주기 위해 r'/bclass/b'처럼 Raw string임을 알려주는 기호 r를 반드시 붙여 주어야 한다.

>>> p = re.compile(r'\bclass\b')
>>> print(p.search('no class at all'))
<re.Match object; span=(3, 8), match='class'>
>>> print(p.search('the declassified algrorithm'))
None
>>> print(p.search('one subclass is'))
None

 

  • \B: \B 메타 문자는 \b 메타 문자와 반대의 경우이다. 즉 whitespace로 구분된 단어가 아닌 경우에만 매치된다.

>>> p = re.compile(r'\Bclass\B')
>>> print(p.search('no class at all'))
None
>>> print(p.search('the declassified algorithm'))
<_sre.SRE_Match object at 0x01F6FA30>
>>> print(p.search('one subclass is'))
None

 

 

그루핑
# ABC 문자열이 계속해서 반복되는지 조사하는 정규식을 작성하고 싶을 때 필요한 것이 바로 그루핑이며,
# 그룹을 만들어 주는 메타 문자는 바로 ( )이다.
>>> p = re.compile('(ABC)+')
>>> m = p.search('ABCABCABC OK?')
>>> print(m)
<re.Match object; span=(0, 9), match='ABCABCABC'>
>>> print(m.group(0))
ABCABCABC

# '\w+\s+\d+[-]\d+[-]\d+'은 '이름 + "" + 전화번호' 형태의 문자열을 찾는 정규식이다.
# 이렇게 매치된 문자열 중에서 이름만 뽑아내고 한다면 다음과 같이 할 수 있다.
>>> p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
>>> m = re.search("park 010-1234-5678")
>>> print(m.group(1))
park

# 이름에 해당하는 '\w+' 부분을 그룹 '(\w+)'으로 만들면 match 객체의 group(인덱스) 메서드를 사용하여 그루핑된 부분의 문자열만 뽑아낼 수 있다.
# 이번에는 전화번호 부분을 추가로 그룹 '(\d+[-]\d+[-]\d+)'로 만들어 group(2)를 사용하여 전화번호만 뽑아낼 수 있다.
>>> p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
>>> m = re.search("park 010-1234-5678")
>>> print(m.group(2))
010-1234-5678

# 만약 전화번호 중에서 국번만 뽑아내고 싶으면 다음과 같이 국번 부분을 또 그루핑하면 된다.
>>> p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = re.search("park 010-1234-5678")
>>> print(m.group(3))
010

# 위 예에서 볼 수 있듯이 '(\w+)\s+((\d+)[-]\d+[-]\d+)'처럼 그룹을 중첩되게 사용하는 것도 가능하다.
# 그룹이 중첩되어 있는 경우는 바깥쪽부터 시작하여 안쪽으로 들어갈수록 인덱스가 증가한다.

 

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

# 그룹의 또 하나 좋은 점은 한 번 그루핑한 문자열을 재참조할 수 있다는 점이다.
# 정규식 '(\b\w+)\s+\1'은 '(그룹) + "" + 그룹과 동일한 단어'와 매치됨을 의미한다.
# 이렇게 정규식을 만들게 되면 2개의 동일한 단어를 연속적으로 사용해야만 매치되며,
# 이것을 가능하게 해주는 것이 바로 재참조 메타 문자인 \1이다.
>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('Paris in the the spring').group()
'the the'

 

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

# 정규식 안에 그룹이 10개 이상만 되어도 매우 혼란스러울 것이다.
# 거기에 더해 정규식이 수정되면서 그룹이 추가, 삭제되면 그 그룹을 인덱스로 참조한 프로그램도 모두 변경해 주어야 하는 위험도 갖게 된다.
# 이러한 이유로 정규식은 그룹을 만들 때 그룹 이름을 지정할 수 있게 했으며, 그룹에 이름을 지정하고 참조해 보자.
>>> p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-5678")
>>> print(m.group("name"))
park

# 위 예에서 볼 수 있듯이 name이라는 그룹 이름을 참조할 수 있다.
# 그룹 이름을 사용하면 정규식 안에서 재참조하는 것도 가능하다.
# 재참조할 때에는 (?P=그룹 이름)이라는 확장 구문을 사용해야 한다.
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'

 

 

전방 탐색
정규식 종류 설명
(?=...) 긍정형 전방 탐색 ...에 해당하는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
(?!...) 부정형 전방 탐색 ...에 해당하는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다.

 

  • 긍정형 전방 탐색

# 정규식 중 :에 해당하는 부분에 긍정형 전방 탐색 기법을 적용하여 (?=:)으로 변경하였다.
# 이렇게 되면 기존 정규식과 검색에서는 동일한 효과를 발휘하지만 :에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아
# 검색 결과에서는 :이 제거된 후 돌려주는 효과가 있다.
>>> p = re.compile(".+(?=:)")
>>> m = p.search("http://google.com")
>>> print(m.group())
http

# 이 정규식은 '파일 이름 + . + 확장자'를 나타내는 정규식이며,
# foo.bar, autoexec.bat, sendmail.cf 같은 형식의 파일과 매치될 것이다.
.*[.].*$

# 이 정규식에 확장자가 'bat인 파일은 제외해야 한다'는 조건을 추가해 보자.
# 이 정규식은 확장자가 b라는 문자로 시작하면 안된다는 의미이며, foo.bar라는 파일마저 걸러 낸다.
.*[.][^b].*$

# 이 정규식은 | 메타 문자를 사용하여 확장자의 첫 번째 문자가 b가 아니거나 두 번째 문자가 a가 아니거나 세 번째 문자가 t가 아닌 경우를 의미한다.
# 이 정규식에 의하여 foo.bar는 제외되지 않고 autoexec.bat은 제외되어 만족스러운 결과를 돌려준다.
# 하지만 이 정규식은 아쉽게도 sendmail.cf처럼 확장자의 문자 개수가 2개인 케이스를 포함하지 못하는 오동작을 하기 시작한다.
.*[.]([^b]..|.[^a].|..[^t])$

# 확장자의 문자 개수가 2개여도 통과되는 정규식이 만들어졌다.
# 하지만 정규식은 점점 더 복잡해지고 이해하기 어려워진다.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$

 

  • 부정형 전방 탐색

# 긍정형 전방 탐색으로 모든 조건을 만족하는 정규식을 구현하려면 패턴은 더욱더 복잡해질 것이다.
# 따라서 이러한 상황의 구원 투수는 바로 부정형 전방 탐색이며, 부정형 전방 탐색을 사용하면 다음과 같다.
# 확장자가 bat가 아닌 경우에만 통과된다는 의미이며, bat 문자열이 있는지 조사하는 과정에서 문자열이 소비되지 않으므로 bat가 아니라고 판단되면 그 이후 정규식 매치가 진행된다.
.*[.](?!bat$).*$

# exe 역시 제외하라는 조건이 추가되더라도 다음과 같이 간단히 표현할 수 있다.
.*[.](?!bat$|exe$).*$

 

 

문자열 바꾸기
# sub 메서드를 사용하면 정규식과 매치되는 부분을 다른 문자로 쉽게 바꿀 수 있다.
# sub 메서드의 첫 번째 매개변수는 '바꿀 문자열'이 되고, 두 번째 매개변수는 '대상 문자열'이 된다.
# 다음과 같이 blue 또는 white 또는 red라는 문자열이 colour라는 문자열로 바뀌는 것을 확인할 수 있다.
>>> p = re.compile('(blue|white|red)')
>>> p.sub('colour', 'blue socks and red shoes')
'colour socks and colour shoes'

# 그런데 딱 한 번만 바꾸고 싶은 경우도 있다.
# 이렇게 바꾸기 횟수를 제어하려면 다음과 같이 세 번째 매개변수로 count값을 넘기면 된다.
>>> p.sub('colour', 'blue socks and red shoes', count=1)
'colour socks and red shoes'

 

  • sub 메서드를 사용할 때 참조 구문 사용하기

# sub 메서드를 사용할 때 참조 구문을 사용할 수 있다.
# 다음 예는 '이름 + 전화번호'의 문자열을 '전화번호 + 이름'으로 바꾸는 예이다.
# sub의 바꿀 문자열 부분에 '\g<그룹 이름>'을 사용하면 정규식의 그룹 이름을 참조할 수 있게 된다.
>>> p = re.compile(r"(?P<name>\w+)\s+(?P<phone>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<phone>\g<name>", "park 010-1234-5678"))
010-1234-5678 park

# 다음과 같이 그룹 이름 대신 참조 번호를 사용해도 마찬가지 결과를 돌려준다.
>>> p = re.compile(r"(?P<1>\w+)\s+(?P<2>(\d+)[-]\d+[-]\d+)")
>>> print(p.sub("\g<2>\g<1>", "park 010-1234-5678"))
010-1234-5678 park

 

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

# sub 메서드의 첫 번째 매개변수로 함수를 넣을 수도 있다.
# hexrepl 함수는 match 객체를 입력으로 받아 16진수로 변환하여 돌려주는 함수이다.
# sub의 첫 번째 매개변수로 함수를 사용할 경우 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력된다.
# 그리고 매치되는 문자열은 함수의 반환 값으로 바뀌게 된다.
>>> def hexrepl(match):
...    "Return the hex string for a decimal number"
...    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.'

 

 

Greedy vs Non-Greedy
# '<.*>' 정규식의 매치 결과로 <html> 문자열을 돌려주기를 기대했을 것이다.
# 하지만 * 메타 문자는 매우 탐욕스러워서 매치할 수 있는 최대한의 문자열인 <html><head><title>Title</title> 문자열을 모두 소비해 버렸다.
>>> 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 문자인 ?를 사용하면 *의 탐욕을 제한할 수 있다.
# non-greedy 문자인 ?는 *?, +?, ??, {m,n}?와 같이 사용할 수 있다.
# 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 한다.
>>> print(re.match('<.*?>', s).group())
<html>

'PYTHON' 카테고리의 다른 글

[PYTHON] 15. 파이썬 Final 20제  (2) 2021.03.03
[PYTHON] 13. 파이썬으로 간단한 프로그램 만들기  (0) 2021.02.26
[PYTHON] 12. 외장 함수  (0) 2021.02.25
[PYTHON] 11. 내장 함수  (0) 2021.02.25
[PYTHON] 10. 예외 처리  (0) 2021.02.24