220915 Day15

유예지·2022년 9월 15일

(2) re (regular expression) 모듈

-re.compile을 사용하여 정규표현식을 컴파일 한다
-re.compile("패턴") : 괄호 안의 패턴(=조건)과 매치되는지 조사해라, 매치되는 것을 찾아라
-객체 p : re.compile 한 결과

>>> p = re.compile("^[a-z].+[A-Z]$")
    type(p)
re.Pattern

>>> for word in words:
        p = re.compile("^[a-z].+[A-Z]$")
        p.sub("#", word)
    print(words)    
#for문 안의 코드 두줄 : re.sub("^[a-z].+[A-Z]$", "#", word) 와 동일한 표현
#complie로 코딩하는 것을 더 권장 -> 메모리를 절약할 수 있고, 속도가 더 빠르다

* 컴파일된 패턴 객체(p)는 4가지 메서드를 제공한다

① match() : 문자열의 '처음부터' 정규식과 매치되는지 조사

>>> p = re.compile("[a-z]+")

>>> p.match("python")
<re.Match object; span=(0, 6), match='python'>
#"python" 문자열은 컴파일 된 정규식 "[a-z]+" 에 부합되므로 match 객체를 돌려준다
#span=(0, 6) : 매치된 문자열의 시작과 끝을 알려준다 (인덱스 번호)

>>> re.match("[a-z]+", "python")
<re.Match object; span=(0, 6), match='python'>

>>> p.match("pythonPYTHON1234")
<re.Match object; span=(0, 6), match='python'>

>>> p.match("PYTHONpython1234")
#아무런 결과가 출력되지 않는다
#match는 문자열의 '처음부터' 매치되는지를 조사한다

② search() : '문자열 전체' 를 조사

>>> p.search("PYTHONpython1234")
<re.Match object; span=(6, 12), match='python'>
#search는 문자열 전체를 조사하기 때문에 매치되는 부분을 확인할 수 있다

>>> p.search("PYTHONpython1234   pythonPYTHON1234")
<re.Match object; span=(6, 12), match='python'>

* match 메서드와 search 메서드

문자열의 처음부터 검색할지의 여부에 따라 다르게 사용해야한다
정규식과 매치될 때는 match 객체를 돌려주고, 매치되지 않을 때는 None을 돌려준다

③ findall() : 정규식과 매치되는 '모든 문자열(substring)' 을 '리스트' 로 돌려준다

문자열 안에 띄어쓰기로 구분되어있는 단어를 각각 정규식과 매치해서 리스트로 돌려준다

>>> p.findall("PYTHONpython1234   pythonPYTHON1234")
['python', 'python']

④ finditer() : 정규식과 매치되는 '모든 문자열(substring)' 을 '반복 가능한 객체' 로 돌려준다

-주로 for 문이 함께 사용된다
-findall() 과 동일하지만 그 결과로 반복 가능한 객체(iterator object)를 돌려준다
-반복 가능한 객체가 포함하는 각각의 요소는 match 객체이다

>>> p.finditer("PYTHONpython1234   pythonPYTHON1234")
<callable_iterator at 0x1aaab276020>

>>> for i in p.finditer("PYTHONpython1234   pythonPYTHON1234"):
        print(i)
<re.Match object; span=(6, 12), match='phthon'>
<re.Match object; span=(19, 25), match='phthon'>        

* match 객체의 메서드

>>> p = re.compile("[a-z]+")
>>> p.match("pythonPYTHON1234 PYTHONpython1234")
<re.Match object; span=(0, 6), match='python'>

#group() : 매치된 문자열을 찾아서 보여준다
>>> p.match("pythonPYTHON1234 PYTHONpython1234").group()
'python'

#span() : 매치된 문자열의 (시작, 끝)에 해당하는 튜플을 돌려준다
>>> p.match("pythonPYTHON1234 PYTHONpython1234").span()
(0, 6)

#start() : 매치된 문자열의 시작 위치를 알려준다
>>> p.match("pythonPYTHON1234 PYTHONpython1234").start()
0
#match 메서드를 수행한 결과로 돌려준 match 객체의 start() 결괏값은 항상 0 이다
#match 메서드는 항상 문자열의 시작부터 조사하기 때문이다
#search 메서드를 사용한다면 start() 값은 다르게 나올 것이다

#end() : 매치된 문자열의 끝 위치를 알려준다
>>> p.match("pythonPYTHON1234 PYTHONpython1234").end()
6

* search 객체의 메서드

>>> p = re.compile("[a-z]+")

>>> p.search("PYTHONpython1234").group()
>>> p.search("PYTHONpython1234").span()
>>> p.search("PYTHONpython1234").start()
>>> p.search("PYTHONpython1234").end()

* 전화번호 골라내기

>>> data = ["010-1234-5678", "010-1234-5679", "010-1234-5670", "010-1234-670", "02-334-2243"]

#핸드폰 전화번호 골라내기
>>> for i in data:
		print(re.search("010-\d{4}-\d{4}", i))
<re.Match object; span=(0, 13), match='010-1234-5678'>
<re.Match object; span=(0, 13), match='010-1234-5679'>
<re.Match object; span=(0, 13), match='010-1234-5670'>
None
None

>>> data = ["010-1234-5678", "010-1234-5679", "010-1234-5670", "010-1234-670", "02-334-2243"]

    def is_phone_number(data):
        phone_number = []
        for i in data:
            if re.search("010-\d{4}-\d{4}", i):
                phone_number.append(i)

        return phone_number

    is_phone_number(data)
['010-1234-5678', '010-1234-5679', '010-1234-5670']

#finditer , group 을 사용하여
>>> data = "010-1234-5678, 010-1234-5679, 010-1234-5670, 010-1234-670, 02-334-2243"

    def is_phone_number(data):
        phone_number = []
        for i in re.finditer("010-\d{4}-\d{4}", data):
            phone_number.append(i.group())
        return phone_number

    is_phone_number(data)
['010-1234-5678', '010-1234-5679', '010-1234-5670']

#findall 을 사용하여
>>> data = "010-1234-5678, 010-1234-5679, 010-1234-5670, 010-1234-670, 02-334-2243"

    re.findall("010-\d{4}-\d{4}", data)
['010-1234-5678', '010-1234-5679', '010-1234-5670']    
#'하세요', '하지요', '싶어요' 의 span()을 출력
>>> msg = "안녕하세요. 제 소개부터 드리도록 하지요. 저는 의미있는 일을 하고 싶어요. 건강하세요."

    p = re.compile("하세요|하지요|싶어요")

    for m in p.finditer(msg):
        print(m.span())
(2, 5)
(19, 22)
(38, 41)
(45, 48)

>>> for m in re.finditer("하세요|하지요|싶어요", msg):
	    print(m.span())

(3) group (grouping 그루핑) : ()

#이름 + " " + 전화번호 형태의 문자열
>>> sentence = "park 010-1234-1234"
    p = re.compile(r'(\w+)\s((\d+)[-]\d+[-]\d+)')

>>> p.search(sentence)
<re.Match object; span=(0, 18), match='park 010-1234-1234'>
  • #r'...' : Raw string 임을 알려주는 기호
    -예 : \b 메타 문자의 경우 -> \b는 파이썬 리터럴 규칙에 의하면 백스페이스(Backspace)를 의미하므로, 백스페이스가 아닌 단어 구분자 역할을 하는 메타 문자임을 알려주기 위해
    r'\bclass\b' 처럼 앞에 기호 r 을 반드시 붙여준다

* group(인덱스) : 그루핑된 부분의 문자열만 뽑아낼 수 있다

group(0) : 매치된 전체 문자열
group(n) : n번째 그룹에 해당하는 문자열

>>> p.search(sentence).group(0)
'park 010-1234-1234'

>>> print(p.search(sentence).group(2), p.search(sentence).group(1))
010-1234-1234 park

>>> print(p.search(sentence).group(3))
010

* 그루핑된 문자열에 이름 붙이기 : (?P<그룹 이름>...)

정규식 안에 그룹이 많아지는 경우, 정규식이 수정되면서 그룹이 추가 또는 삭제되면 그 그룹을 인덱스로 참조한 프로그램도 모두 변경해 주어야하는 위험이 생길 수 있다
-> 그룹에 '이름(Name Groups)'을 지정해줄 수 있다

>>> sentence = "park 010-1234-1234"
    p = re.compile(r'(\w+)\s((\d+)[-]\d+[-]\d+)')

#'이름(group(1))'에 'family', '010(group(3))'에 'phone_first'
#(\w+) -> (?P<family>\w+), (\d+) -> (?P<phone_first>\d+)
>>> sentence = "park 010-1234-1234"
    p = re.compile(r'(?P<family>\w+)\s((?P<phone_first>\d+)[-]\d+[-]\d+)')
    
>>> p.search(sentence).group("family")
'park'

>>> p.search(sentence).group("phone_first")
010
  • 그룹 이름을 사용하면 정규식 안에서 재참조 하는 것이 가능하다
    재참조 할 때는 (?P=그룹 이름) 이라는 확장 구문을 사용한다
>>> p = re.compile(r'(?P<word>\b\w+)\s+(?P=word)')
    p.search('Paris in the the spring').group()
'the the'    

* 전방 탐색 (Lookahead Assertions)

>>> p = re.compile(".+:")   #':' 앞의 모든 문자
    m = p.search("http://google.com")
    m
<re.Match object; span=(0, 5), match='http:'>

>>> m.group()
'http:'
#정규식 ".+:" 과 일치하는 문자열로 'http:' 를 돌려주었다 -> ':'를 포함하고 있다
  • 검색 결과에서 ':'을 제외하고 출력하려면 어떻게 해야할까? -> 전방 탐색을 한다
#1. 긍정형 전방 탐색 : (?=...)
#'...' 에 해당하는 정규식과 매치되어야 하며, 조건이 통과되어도 검색 결과에서는 제외된다
>>> p = re.compile(".+(?=:)")
    m = p.search("http://google.com")
    m.group()
'http'

#2. 부정형 전방 탐색 : (?!...)
#'...' 에 해당하는 정규식과 매치되지 않아야 하며, 조건이 통과되어도 검색 결과에서는 제외된다
>>> sent = "aaa.exe\nbbb.pdf\nccc.hwp"
    p = re.compile(".*[.](?!exe$).*$")
    c = p.search(sent)
    c.group()
'ccc.hwp'    

* 후방 탐색 : (?<=...)

>>> p = re.compile("(?<=:).+")
    m = p.search("http://google.com")
    m.group()
'//google.com'   #':'이 제외되고 출력되었다

>>> p = re.compile("(?<=ll).+")
    m = p.search("hello2022")
    m.group()
'o2022'

>>> p = re.compile("(?=ll).+")
    m = p.search("hello2022")
    m.group()
'llo2022'

>>> p = re.compile(".+(?=ll)")
    m = p.search("hello2022")
    m.group()
'he'

>>> p = re.compile(".+(?<=ll)")
    m = p.search("hello2022")
    m.group()
'hell'    

후방 탐색에서 '<' 를 빼면 '...'에 해당되는 부분이 함께 출력이 되고,
전방 탐색에서 '<' 를 붙이면 '...'에 해당되는 부분이 함께 출력이 된다

#'log:' 를 기준으로 전방 탐색, 후방 탐색
>>> text = '''
    2018-05-12 00:00:01 ABC DEFG log: this is python
    2018-05-12 00:00:02 ABC DEFG HI log: this is python
    2018-05-12 00:00:03 ABC DEFG HI JKL log: this is python
    '''
    
#전방 탐색
>>> p = re.compile('.+(?=log:)')
    for i in p.finditer(text):
        print(i.group())
2018-05-12 00:00:01 ABC DEFG 
2018-05-12 00:00:02 ABC DEFG HI 
2018-05-12 00:00:03 ABC DEFG HI JKL 

>>> p = re.compile('.+(?=log:)')
    p.findall(text)
['2018-05-12 00:00:01 ABC DEFG ',
 '2018-05-12 00:00:02 ABC DEFG HI ',
 '2018-05-12 00:00:03 ABC DEFG HI JKL ']
 
#후방 탐색
>>> p = re.compile('(?<=log:).+')
    for i in p.finditer(text):
        print(i.group())
 this is python
 this is python
 this is python
 
>>> p = re.compile('(?<=log:).+')
    p.findall(text)
[' this is python', ' this is python', ' this is python']

  • sub 메서드를 사용할 때 참조 구문 사용하기
>>> 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    
  • ['forta.com', 'forta.com'] 출력하기
>>> data = '''
    http://www.forta.com
    https://mail.forta.com
    '''
    
>>> p = re.compile(r"f\w+[.]\w{3}")
    p.findall(data)
['forta.com', 'forta.com']    

>>> p = re.compile(r".+f")
    p.findall(data)
['http://www.f', 'https://mail.f']

>>> p = re.compile(r".+(?=f)")
	p.findall(data)
['http://www.', 'https://mail.']

>>> p = re.compile(r"(?=f).+")
    p.findall(data)
['forta.com', 'forta.com']

>>> p = re.compile(r"(?<=f).+")
	p.findall(data)
['orta.com', 'orta.com']    

* Greedy, Non-Greedy

greedy : 탐욕스러운

#Greedy : 최대한의 문자열을 소비한다
>>> data = ["soso", "sososo", "sosososo", "sososososo"]
    p = re.compile(r'(so){3,5}')   #'so'가 3번 이상 5번 이하 반복
    for i in data:
        print(p.search(i))
None
<re.Match object; span=(0, 6), match='sososo'>
<re.Match object; span=(0, 8), match='sosososo'>
<re.Match object; span=(0, 10), match='sososososo'>

#Non-Greedy : '?' 문자를 사용하여 제한한다
>>> data = ["soso", "sososo", "sosososo", "sososososo"]
    p = re.compile(r'(so){3,5}?')
    for i in data:
        print(p.search(i))
None
<re.Match object; span=(0, 6), match='sososo'>
<re.Match object; span=(0, 6), match='sososo'>
<re.Match object; span=(0, 6), match='sososo'>

0개의 댓글