일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 실전알고리즘
- ubuntu
- 모두를 위한 딥러닝 강좌 시즌1
- 자료구조 및 실습
- 프로그래머스
- 그리디
- 전산기초
- CS231n
- MySQL
- 구현
- cs
- pytorch
- 머신러닝
- ssd
- 1단계
- 파이썬
- 딥러닝
- AWS
- C++
- 이것이 코딩테스트다 with 파이썬
- 코드수행
- 3단계
- docker
- STL
- 2단계
- SWEA
- Object detection
- 백준
- Python
- test-helper
- Today
- Total
곰퓨타의 SW 이야기
[python] 강력한 정규 표현식 뿌시기 본문
드디어 점프 투 파이썬 2회독을 마쳤다..!
마지막 부분 또한 정규식 관련된 곳으로,, 내가 거의 모르는 미지의 세계여서 책을 그대로 따라가보고자 한다.
오늘 마지막으로 정규식을 정리하고,,, 파이썬을 자유자재로 사용할 수 있었으면 좋겠다 ❗️
[내사랑 점프투 파이썬❣️]
위키독스
온라인 책을 제작 공유하는 플랫폼 서비스
wikidocs.net
메타문자
+, *, [], {}는 매치가 진행될 때 매치되고 있는 문자열의 위치가 변경된다. (소비된다.)
|, ^, $, \A, \Z, \b, \B와 같은 문자열 소비가 없는 메타 문자에 대해 알아보자.
1. |
|는 or 의미를 가진다. A|B는 A 또는 B 라는 의미를 가진다.
>>> p = re.compile('Crow|Servo')
>>> m = p.match('CrowHello')
>>> print(m)
<re.Match object; span=(0, 4), match='Crow'>
2. ^
^는 문자열의 맨 처음과 일치함을 의미한다.
>>> print(re.search('^Life', 'Life is too short'))
<re.Match object; span=(0, 4), match='Life'>
>>> print(re.search('^Life', 'My Life'))
None # 'My Life'는 맨 처음에 Life가 나오는 것이 아니므로
3. $
$는 ^와 반대로 문자열 끝과의 매치를 의미한다.
>>> 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 # short이 문자열 맨 끝에 있는 것이 아니므로 매치되지 않는다.
※ ^ 또는 $ 문자를 메타 문자가 아닌 문자 그 자체로 매치하고 싶은 경우에는 \^, \$ 로 사용하면 된다.
4. \A
\A는 문자열의 처음과 매치됨을 의미한다.
^는 re.MULTILINE 옵션을 사용할 때 각 줄의 처음과 매치되지만
\A는 줄과 상관없이 전체 문자열의 처음하고만 매치된다.
5. \Z
\Z는 문자열의 마지막과 매치됨을 의미한다.
\A 처럼 re.MULTILINE 옵션을 사용하더라도 $와 다르게 전체 문자열의 끝과 매치된다.
6. \b
\b는 단어구분자로, 보통 공백으로 구분된다.
>>> p = re.compile(r'\bclass\b') # \b는 백스페이스를 의미하므로, raw string 임을 알리기 위한 r
>>> print(p.search('no class at all'))
<re.Match object; span=(3, 8), match='class'>
>>> print(p.search('the declassified algorithm'))
None # class문자열은 있지만 공백으로 구분되지 않으므로 매치x
7. \B
\B 메타 문자는 \b 메타문자랑 반대이다. 구분된 단어가 아닌 경우에만 매치된다.
>>> p = re.compile(r'\Bclass\B')
>>> print(p.search('no class at all'))
None
>>> print(p.search('the declassified algorithm'))
<re.Match object; span=(6, 11), match='class'>
>>> 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())
ABCABCABC
예시
\w+\s+\d+[-]\d+[-]\d+: 이름 + " " + 전화번호 형태의 문자열을 찾는 정규식이다.
그룹은 매치된 문자열 중 특정 부분의 문자열만 뽑아내기에 용이하다.
만약 '이름'부분만 뽑아내려하면 다음과 같이 하면 된다.
이름에 해당하는 \w+를 (\w+)로 만들면 match 객체의 group(인덱스) 메서드를 사용하여 그루핑된 문자열만 뽑아낼 수 있다.
>>> p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(1))
park
group(인덱스) | 설명 |
group(0) | 매치된 전체 문자열 |
group(1) | 첫 번째 그룹에 해당되는 문자열 |
group(2) | 두 번째 그룹에 해당되는 문자열 |
group(n) | n 번째 그룹에 해당되는 문자열 |
(\w+) 다음 그룹은 (\d+[-]\d+[-]\d+) 이다. 따라서, group(2)를 통해 전화번호만 뽑아낼 수 있다.
>>> p = re.compile(r"(\w+)\s+(\d+[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(2))
010-1234-1234
국번만 뽑아내고 싶으면 다음과 같이해서 뽑아낼 수 있다.
중첩되게 만들면 바깥쪽에서 안쪽으로 갈수록 인덱스가 증가한다 !
>>> p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group(3))
010
1. 그루핑된 문자열 재참조
정규식 (\b\w+)\s+\1은 (그룹) + " " + 그룹과 동일한 단어와 매치됨을 의미한다.
2개의 동일한 단어를 연속적으로 사용해야만 매치가 된다.
첫 번째 그룹은 \1로 가르키고, 2번째 그룹은 \2로 참조가 가능하다.
>>> p = re.compile(r'(\b\w+)\s+\1')
>>> p.search('Paris in the the spring').group()
'the the'
2. 그루핑된 문자열에 이름 붙이기
그룹을 만들 때 그룹 이름을 지정할 수 있다.
(\w+)\s+((\d+)[-]\d+[-]\d+)
->(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)
(\w+)라는 그룹에 name이라는 이름을 붙인 것이다.
즉 그룹에 이름을 붙이는 방법은 다음과 같다.
(?P<그룹명>...)
이름을 지정하고 참조하는 예는 다음과 같다.
>>> p = re.compile(r"(?P<name>\w+)\s+((\d+)[-]\d+[-]\d+)")
>>> m = p.search("park 010-1234-1234")
>>> print(m.group("name"))
park
그룹 이름을 활용하면 (?P=그룹이름)을 통해 정규식 안에서 재참조가 가능하다!
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
>>> p.search('Paris in the the spring').group()
'the the'
전방 탐색
정규식 안에 확장 구문을 활용하는 것이다.. (와 여기는 진짜 어려운 것 같다..)
- 긍정형 전방 탐색((?=...)) - ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
- 부정형 전방 탐색((?!...)) - ...에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소비되지 않는다.
정규식 .+:과 일치하는 문자열로 http:를 돌려주었다. 만약 http:라는 검색 결과에서 :을 제외하고 출력하려면 어떻게 해야 할까?
--> 해결하기 위해 나온 것이 전방탐색이다.
>>> p = re.compile(".+:")
>>> m = p.search("http://google.com")
>>> print(m.group())
http:
1. 긍정형 전방 탐색
긍정형 전방 탐색을 사용하면 http:의 결과를 http로 바꿀 수 있다.
아래 코드와 같이 정규식을 정의하면 : 에 해당하는 문자열이 정규식 엔진에 의해 소비되지 않아(검색에는 포함되지만 검색 결과에는 제외됨) 검색 결과에서는 :이 제거된 후 돌려주는 효과가 있다.
".+:"
-> ".+(?=:)"
>>> p = re.compile(".+(?=:)")
>>> m = p.search("http://google.com")
>>> print(m.group())
http
.*[.].*$
파일 이름 + . + 확장자를 나타내는 정규식이다.
foo.bar, autoexec.bat, sendmail.cf 와 같은 형식의 파일과 매치가 된다.
만약 "bat인 파일은 제외해야 한다" 는 조건이 추가되었다고 생각해보자.
.*[.]([^b].?.?|.[^a]?.?|..?[^t]?)$
이는 bat파일만 제외하였는데도 많이 복잡해진 것을 알 수 있다. 더 많은 파일 확장자를 제외하고 싶은 경우, 훨씬 패턴이 말도 안뙤게 복잡해질 것이다. (사실 벌써 암호문 같다..ㅎㅎ)
2. 부정형 전방 탐색
긍정형 전방 탐색의 마지막 예시는 부정형 전방 탐색을 사용하면 다음과 같이 간단하게 처리된다.
.*[.](?!bat$).*$
bat 문자열이 있는지 조사하는 과정에서 문자열이 소비되지 않으므로 bat가 아니라고 판단되면 그 이후 정규식 매치가 진행된다.
exe 도 제외하고 싶은 경우 다음과 같이 간단히 표현이 가능하다!
.*[.](?!bat$|exe$).*$
문자열 바꾸기
sub 메서드는 정규식과 매치되는 부분을 다른 문자로 쉽게 바꿀 수 있다.
>>> 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'
subn은 sub와 동일한 기능을 하지만 반환 결과를 튜플로 돌려준다.
돌려준 튜플의 첫 번쩨 요소는 변경된 문자열이고 두 번째 요소는 바꾸기가 발생한 횟수이다.
>>> p = re.compile('(blue|white|red)')
>>> p.subn( 'colour', 'blue socks and red shoes')
('colour socks and colour shoes', 2)
1. 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"))
010-1234-1234 park
\g<그룹이름>으로 그룹 이름 참조가 가능하다!
>>> 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
참조 번호로도 참조가 가능하다 !
2. sub 메서드의 매개 변수로 함수 넣기
>>> def hexrepl(match):
... 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.'
hexrepl 함수는 match 객체를 입력으로 받아, 16진수로 변환해주는 것이다. sub의 첫 번ㄴ째 매개변수로 함수를 사용하면, 해당 함수의 첫 번째 매개변수에는 정규식과 매치된 match 객체가 입력된다. 매치되는 문자열은 함수의 반환 값으로 바뀐다.
(여기선 숫자들이 match 되어 16진수로 바뀐 것을 볼 수 있는 것 같다 !)
정규식에서의 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>
* 메타문자는 탐욕스러워서 매치할 수 있는 최대한의 문자열인 <html><head><title>Title</title> 문자열을 모두 소비한다. <html>만 돌려주기를 바라는 경우 다음과 같이 non-greedy 문자인 ? 를 사용하면 * greedy를 억제할 수 있다.
>>> print(re.match('<.*?>', s).group())
<html>
?는 *?, +?, ??, {m,n}?과 같이 사용할 수 있고, 이는 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 수행한다.
정규식은 그대로 따라하는데도 진짜 어려운 것 같다...
이제 코테 준비에 몰두하면서 모두를 위한 딥러닝 강좌를 익혀야겠다..!😊
'TIL > 코테개념_python' 카테고리의 다른 글
[python] 입출력 및 주요라이브러리 문법 (0) | 2021.04.13 |
---|---|
[python] 정규 표현식 시작하기 뿌시기 (0) | 2021.01.07 |
[python] 예외 처리 뿌시기 (0) | 2021.01.07 |
[python] 패키지 뿌시기 (0) | 2021.01.07 |
[python] 모듈 뿌시기 (0) | 2021.01.07 |