#!/usr/bin/env python # coding: utf-8 # ## 07-1 정규 표현식 살펴보기 # # - 정규 표현식(Regular Expressions), 정규식 # # ### 정규 표현식은 왜 필요한가? # # > 주민등록번호를 포함하고 있는 텍스트에서 모든 주민등록번호의 뒷자리를 * 문자로 변경하시오. # # - 정규식을 모르는 경우 # 1. 전체 텍스트를 공백 문자로 나눈다(split). # 2. 나누어진 단어들이 주민번호 형식인지 조사. # 3. 단어가 주민번호 형식이면 뒷자리를 * 로 변환 # 4. 나누어진 단어들을 다시 조립 # In[2]: data = ''' park 800905-1049118 kim 700905-1059119 ''' result = [] for line in data.split('\n'): word_result = [] for word in line.split(' '): if len(word) == 14 and word[:6].isdigit() and word[6] == '-' and word[7:].isdigit(): word = word[:7] + "*******" word_result.append(word) result.append(" ".join(word_result)) print("\n".join(result)) # In[3]: # 정규식을 사용하면 import re data = ''' park 800905-1049118 kim 700905-1059119 ''' pat = re.compile("(\d{6})[-]\d{7}") print(pat.sub("\g<1>-*******", data)) # --- # ## 07-2 정규 표현식 시작하기 # # ### 정규 표현식의 기초, 메타 문자 # # - 정규 표현식에는 다음과 같은 메타 문자가 있다. # # > . ^ $ * + ? { } [ ] \ | ( ) # # #### 문자 클래스 [ ] # # 문자 클래스로 만들어진 정규식은 "[ 와 ] 사이의 문자들과 매치" 라는 의미를 갖는다. # # ***문자 클래스를 만드는 메타 문자인 [ 와 ] 사이에는 어떤 문자도 들어갈 수 있다.*** # # 즉, 정규 표현식이 [abc] 라면 이 표현식의 의미는 "a, b, c 중 한개의 문자와 매치"를 뜻한다. # # - "a"는 정규식과 일치하는 문자인 "a"가 있으므로 매치 # - "before"는 정규식과 일치하는 문자인 "b"가 있으므로 매치 # - "dude"는 정규식과 일치하는 문자인 a, b, c 중 어느 하나도 포함하고 있지 않으므로 매치되지 않음 # # [ ] 안에 하이픈(-)을 사용하면 두 문자 사이의 범위(From - To)를 의마한다. [a-c]는 [abc]와 동일하고, [0-5]는 [012345]와 동일하다. # # - [a-zA-Z]: 알파벳 모두 # - [0-9]: 숫자 # # 메타 문자 ^ 는 반대(not)라는 의미로 주의를 해야 한다. # # - [^0-9]: 숫자가 아닌 문자만 매치 # # #### [자주 사용하는 문자 클래스] # # [0-9], [a-zA-Z] 등과 같이 자주 사용하는 정규식의 경우 다음과 같이 별도의 표기법으로 표현할 수 있다. # # - \d - 숫자와 매치, [0-9]와 동일 # - \D - 숫자가 아닌 것과 매치, [^0-9]와 동일 # - \s - whitespace 문자와 매치, [ \t\n\r\f\v]와 동일, 맨 앞은 공백 문자 # - \S - whitespace 문자가 아닌 것과 매치, [^ \t\n\r\f\v]와 동일 # - \w - 문자+숫자와 매치, [a-zA-Z0-9]와 동일 # - \W - 문자+숫자가 아닌 문자와 매치, [^a-zA-Z0-9]와 동일 # # #### Dot(.) # # Dot(.) 메타 문자는 \n(줄바꿈 문자)를 제외한 모든 문자와 매치된다. # # - a.b - "a + 모든문자 + b" # - aab: 매치 # - a0b: 매치 # - abc: "a"와 "b" 사이에 어떤 문자도 없기 때문에 매치되지 않는다. # # - a[.]b - "모든 문자"라는 의미가 아닌 문자 . 그대로를 의미한다. # # #### 반복(*) # # - ca*t - * 문자 앞의 a가 무한대로 반복될 수 있다는 의미(0번 포함) # - ct, cat, caaaat # # #### 반복(+) # # 최소한 한번 이상은 나와야 하는 반복 # # - ca+t: cat, caaat 는 매치가 되나 ct 는 매치되지 않음 # # #### 반복({m, n}, ?) # # { } 메타 문자를 사용하면 반복 횟수를 고정시킬 수 있다. # # - {m, n}: m부터 n까지 반복 # - {m, }: 반복횟수가 m 이상인 경우 # - {, n}: 반복횟구가 n 이하인 경우 # # # - {1, }: + 와 동일 # - {0, }: * 와 동일 # - ?: {0, 1} 과 동일 # # #### 파이썬에서 정규 표현식을 지원하는 re 모듈 # # 파이썬이 설치될 때 기본 라이브러리로 정규식을 지원하기 위한 re 모듈을 제공한다. # # ``` # import re # p = re.compile('ab*') # ``` # # re.compile 을 이용하여 정규식을 컴파일하고 컴파일된 패턴객체(p)를 이용하여 작업을 수행한다. # # #### 정규식을 이용한 문자열 검색 # # - match(): 문자열의 처음부터 정규식과 매치되는지 조사 # - search(): 문자열 전체를 검색하여 정규식과 매치되는지 조사 # - findall(): 정규식과 매치되는 모든 문자열(substring)을 리스트로 리턴 # - finditer(): 정규식과 매치되는 모든 문자열(substring)을 iterator 객체로 리턴 # # match, search 는 정규식과 매치될 때에는 match 객체를 리턴하고 그렇지 않을 경우에는 None을 리턴한다. # In[2]: import re p = re.compile('[a-z]+') # In[2]: # match m = p.match("python") print(m) # In[3]: m = p.match("3 python") print(m) # In[4]: m = p.match("string goes here") if m: print("match found: ", m.group()) else: print("No match") # In[5]: # search m = p.search("python") print(m) # In[6]: m = p.search("3 python") # 문자열 전체를 검색하기 때문에 매치 print(m) # In[3]: # findall result = p.findall("life is too short") print(result) # In[3]: # finditer result = p.finditer("life is too short") print(result) # In[4]: for r in result: print(r) print(r.group()) # --- # ### match 객체의 메서드 # # - group(): 매치된 문자열을 리턴 # - start(): 매치된 문자열의 시작 위치를 리턴 # - end(): 매치된 문자열의 끝 위치를 리턴 # - span(): 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 리턴 # In[3]: m = p.match("python") # In[4]: m.group() # In[5]: m.start() # In[6]: m.end() # In[7]: m.span() # --- # ### 컴파일 옵션 # # - DOTALL(S): . 이 줄바꿈 문자를 포함하도록 한다. # - IGNORECASE(I): 대소문자를 구분없이 매치한다. # - MULTILINE(M): 여러줄과 매치한다. # - VERBOSE(X): verbose 모드를 사용할 수 있도록 한다. # In[8]: # DOTALL, S import re p = re.compile('a.b') m = p.match('a\nb') print(m) # In[9]: p = re.compile('a.b', re.DOTALL) m = p.match('a\nb') print(m) # In[10]: # IGNORECASE, I p = re.compile('[a-z]', re.I) p.match('python') # In[11]: p.match('python3') # In[12]: p.match('python 3') # In[14]: p.match('3 python') # In[15]: p.match('Python') # In[16]: p.match('PYTHON') # In[17]: # MULTILINE, M 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)) # In[18]: p = re.compile("^python\s\w+", re.M) print(p.findall(data)) # In[19]: # VERBOSE, X charref = re.compile(r'&[#](0[0-7]+|[0-9]+|x[0-9a-fA-F]+);') # In[20]: 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) # - 패턴 객체인 charref 는 동일한 역할을 한다. # - re.VERBOSE 옵션을 사용하면 문자열에 사용된 whitespace는 컴파일 시 제거된다.([] 내에 사용된 것은 제외) # --- # ## 07-3 강력한 정규 표현식의 세계로 # # ### 메타문자 # # 문자열 소모가 없는 메타 문자 # In[21]: # | 메타문자는 or 의 의미돠 동일하다. p = re.compile("Crow|Servo") m = p.match("CrowHello") print(m) # In[22]: # ^ 는 문자열의 맨 처음과 일치함을 의미한다. print(re.search("^Life", "Life is woo short")) # In[23]: print(re.search("^Life", "My Life")) # In[24]: # $ 는 ^ 메타문자와 반대의 경우 print(re.search("short$", "Life is too short")) # In[25]: print(re.search("short$", "Life is too short, you need python")) # #### \A # # - 문자열의 처음과 매치 # - ^ 와 동일한 의미이지만, re.MULTILINE 옵션을 사용할 경우는 라인과 상관없이 전체 문자열의 처음하고만 매치 # # #### \Z # # - 문자열의 끝과 매치 # - $ 와 동일한 의미이지만, re.MULTILINE 옵션 사용 시 라인과 상관없이 전체 문자열의 끝하고만 매치 # In[26]: # \b: 단어 구분자 p = re.compile(r'\bclass\b') # In[27]: print(p.search("no class at all")) # In[28]: print(p.search("the declassified algorithm")) # In[29]: print(p.search("one subclass is")) # In[30]: # \B: \b 와 반대의 경우 매치된다. p = re.compile(r'\Bclass\B') # In[31]: print(p.search("no class at all")) # In[32]: print(p.search("the declassified algorithm")) # In[33]: print(p.search("one subclass is")) # ### 그룹핑 # # - 반복되는 문자열을 찾는 경우 # - 매치된 문자열 중에서 특정 부분의 문자열만 뽑아내긴 위한 경우 # - 그룹핑된 문자열 재참조 # - 그룹을 만들어 주는 메타문자는 '(', ')' # In[1]: import re # In[2]: p = re.compile('(ABC)+') m = p.search('ABCABCABC OK?') print(m) # In[3]: print(m.group()) # In[4]: p = re.compile(r"\w+\s+\d+[-]\d+[-]\d+") m = p.search("park 010-1234-1234") print(m.group()) # In[5]: p = re.compile(r"(\w+)\s+\d+[-]\d+[-]\d+") m = p.search("park 010-1234-1234") print(m.group()) # In[6]: print(m.group(1)) # In[7]: print(m.group(2)) # In[8]: print(m.group(0)) # In[9]: p = re.compile(r"(\w+)\s+((\d+)[-]\d+[-]\d+)") m = p.search("park 010-1234-1234") # In[10]: print(m.group()) print(m.group(0)) print(m.group(1)) print(m.group(2)) print(m.group(3)) print(m.group(4)) # In[11]: p = re.compile(r'(\b\w+)\s+\1') p.search('Paris in the the spring').group() # ### 그룹핑된 문자열에 이름 붙이기 # # - 정규식 내에 그룹이 많아질 경우 관리하기 위해 다음과 같이 그룹명을 지정할 수 있다. # > (?P\w+)\s+((\d+)[-]\d+[-]\d+) # - 달라진 부분은 다음과 같다. (\w+)라는 그룹에 name 이라는 이름을 지정 # > (\w+) --> (?P\w+) # In[12]: p = re.compile(r"(?P\w+)\s+((\d+)[-]\d+[-]\d+)") m = p.search("park 010-1234-1234") print(m.group("name")) # In[13]: # 그룹명으로 재참조 p = re.compile(r'(?P\b\w+)\s+(?P=word)') p.search('Paris in the the spring').group() # ### 전방 탐색 # # - 긍정형 전방 탐색((?=...)) - ... 에 해당되는 정규식과 매치되어야 하며 조건이 통과되어도 문자열이 소모되지 않는다. # - 부정형 전방 탐색((?!...)) - ...에 해당되는 정규식과 매치되지 않아야 하며 조건이 통과되어도 문자열이 소모되지 않는다. # In[14]: p = re.compile(".+:") m = p.search("http://google.com") print(m.group()) # In[15]: # : 제외한 결과를 얻기 위해 전방탐색 적용 p = re.compile(".+(?=:)") m = p.search("http://google.com") print(m.group()) # #### 부정형 전방 탐색 # In[1]: import re # In[3]: p = re.compile(".*[.](?!bat$).*$") m = p.search("test.txt") print(m.group()) # In[4]: m = p.search("test.bat") print(m.group()) # In[5]: m = p.search("test.exe") print(m.group()) # In[6]: p = re.compile(".*[.](?!bat$|exe$).*$") m = p.search("test.txt") print(m.group()) # In[7]: m = p.search("test.bat") print(m.group()) # In[8]: m = p.search("test.exe") print(m.group()) # ### 문자열 바꾸기 # In[9]: p = re.compile('(blue|red|white)') p.sub('color', 'blue socks and red shoes') # In[10]: # 바꾸는 횟수를 제한 p.sub('color', 'blue socks and red shoes', count=1) # #### [sub 메서드와 유사한 subn 메서드] # # - 동일한 기능이지만 결과를 튜플로 리턴한다. # - 리턴되는 튜플의 첫번째 요소는 변경된 문자열이고, 두번째 요소는 바꾸기가 발생한 횟수이다. # In[11]: p.subn('color', 'blue socks and red shoes') # #### sub 메서드 사용 시 참조 구문 사용하기 # In[1]: import re # In[2]: p = re.compile(r"(?P\w+)\s+(?P(\d+)[-]\d+[-]\d+)") print(p.sub("\g \g", "park 010-1234-1234")) # In[3]: # 그룹명 대신 참조번호 이용 p = re.compile(r"(?P\w+)\s+(?P(\d+)[-]\d+[-]\d+)") print(p.sub("\g<2> \g<1>", "park 010-1234-1234")) # #### sub 메서드의 입력 인수로 함수 넣기 # In[4]: def hexrepl(match): "Return the hex string for a decimal number" value = int(match.group()) return hex(value) # In[5]: p = re.compile(r'\d+') p.sub(hexrepl, 'Call 65490 for printing, 49152 for user code.') # ### Greedy vs Non-Greedy # # - \* 메타문자는 매우 탐욕스러워서 매치할 수 있는 최대한의 문자열을 소모한다. # In[6]: s = 'Title' len(s) # In[7]: print(re.match('<.*>', s).span()) # In[8]: print(re.match('<.*>', s).group()) # - < html > 이라는 문자열만 소모되도록 제한하기 위해서는 Non-Greedy 문자인 ? 를 사용한다. # - non-greedy 문자인 ?은 *?, +?, ??, {m,n}?과 같이 사용할 수 있다. # - 가능한 한 가장 최소한의 반복을 수행하도록 도와주는 역할을 한다. # In[9]: print(re.match('<.*?>', s).group())