Wisdom’s Cloud
[PYTHON] 14. 정규 표현식 본문
정규 표현식
-
정규 표현식(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 |