python regular expression(정규표현식)

|

패스트캠퍼스 웹 프로그래밍 수업을 듣고 중요한 내용을 정리했습니다.
개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
이 포스팅에서는 python 정규표현식에 대해 설명합니다.


정규표현식 (Regular Expression)

특정한 패턴에 일치하는 복잡한 문자열을 처리할 때 사용하는 기법

파이썬에서는 표준 모듈 re를 사용해 정규표현식을 사용할 수 있다.

import re

match : 시작(맨 처음)부터 일치하는 패턴찾기

import re
source = 'Lux, the Lady of Luminosity'

m = re.match('Lux', source)
# match의 첫번째 인자에는 패턴이 들어가며, 두번째 인자에는 문자열 소스가 들어간다.
# match()는 소스와 패턴의 일치여부를 확인하며, 일치할 경우 match object를 반환한다.

print(m)
# <_sre.SRE_Match object; span=(0, 3), match='Lux'>

print(m.group())
>>> 'Lux'

# 만약
m = re.match('Lady', source)
print(m)
>>> None

#그런데
m = re.match('Lux, the Lady', source)
print(m.group())
>>> 'Lux, the Lady'

그런데 마지막 패턴 검색보다 훨씬 쉽게 문장을 불러오는 방법이 있다.

m = re.match('.*Lady', source)
>>> 'Lux, the Lady'

여기서 .은 \n제외한 문자 1개, *은 해당 패턴이 0회 이상 올 수 있다는 의미를 지닌다. 즉, 문자 1개 (어떤 문자든) 그게 0회 이상 올 수 있다는 의미를 뜻한다.

0회 이상 올 수 있다는 말은 문자가 아무것도 안와도, 공백이 있어도 다 허용이 된다는 것이다.

search : 첫번째 일치하는 패턴찾기

*없이 Lady하나만을 찾을 경우에는, search()을 사용한다.

m = re.search('Lady', source)

print(m.group())
>>> 'Lady'

findall : 일치하는 모든 패턴 찾기

findall을 사용하면 해당 문자가 리스트로 출력된다.

re.findall('L',source)
>>> ['L', 'L', 'L']

만약 문자가 단순 L이 아니라 L뒤에 문자가 더 나오는 것을 찾고 싶다면

re.findall('L.{2}', source)
>>> ['Lux', 'Lad', 'Lum']

# 혹은
re.findall('L..', source)
>>> ['Lux', 'Lad', 'Lum']

두 방법 모두 같은 결과물을 출력해준다.

split : 패턴으로 나누기

문자열의 split()메서드와 비슷하지만 패턴을 사용할 수 있다.

m = re.split('o', source)
m
>>> ['Lux, the Lady ', 'f Lumin', 'sity']

sub : 패턴 대체하기

문자열이 replace() 메서드와 비슷하지만 패턴을 사용할 수 없다.

m = re.sub('o','!',source)
m
>>> 'Lux, the Lady !f Lumin!sity'

정규표현식의 패턴 문자

패턴 문자
\d 숫자
\D 비숫자
\w 문자
\W 비문자
\s 공백 문자
\S 비 공백 문자
\b 단어경계 (\w와 \W의 경계)
\B 비단어 경계

각 해당 패턴의 기능을 알아보고 싶다면

import string
printable = string.printable

print(re.findall('알아보고 싶은 패턴', printable))

정규표현식의 패턴 지정자 (Pattern specifier)

expr은 정규 표현식을 말한다

패턴 의미  
abc 리터럴 abc  
expr expr  
expr1 expr2 expr1 또는 expr2
. \n을 제외한 모든 문자  
^ 소스 문자열의 시작(문장의 시작)  
$ 소스 문자열의 끝(문장의 끝)  
expr? 0또는 1회의 expr  
expr* 0회 이상의 최대 expr(반복)  
expr+ 1회 이상의 최대 expr  
expr*? 0회 이상의 최소 expr  
expr+? 1회 이상의 최소 expr  
expr{m} m회의 expr  
expr{m,n} m에서 n회의 최대 expr ({0,} = *, {1,} = +, {0,1} = ?)  
expr{m,n}? m에서 n회의 최소 expr  
[abc] a or b or c  
[^abc] not(a or b or c)  
expr1(?=expr2) 뒤에 expr2 오면 expr1에 해당하는 부분  
expr1(?!expr2) 뒤에 expr2 오지 않으면 expr1에 해당하는 부분  
(?<=expr1)expr2 앞에 expr1 오면 expr2에 해당하는 부분  
(?<!expr1)expr2 앞에 expr1 오지 않으면 expr2에 해당하는 부분  

매칭 결과 그룹화

정규표현식 패턴 중 괄호 로 둘러싸인 부분이 있을 경우, 결과는 해당 괄호만의 그룹으로 저장된다.

match객체의 group()함수는 매치된 전체 문자열을 리턴하며, group()함수는 지정된 그룹 리스트를 리턴해준다. group(0)group()과 같은 동작을 하며, group(숫자)는 매치된 숫자번째의 그룹요소를 리턴해준다.

(?P<name>expr)패턴을 사용하면 매칭된 표현식 그룹에 이름을 붙여 사용할 수 있다.

m = re.search(r'(?P<before>\w+)\s+(?P<was>was)\s+(?P<after>\w+)', story)

m.groups()
m.group('before')
m.group('was')
m.group('after')

최소일치와 최대일치

html = '<html><body><h1>HTML</h1></body></html>'

위 항목을

re.findall(r'<.*>', html)
>>> ['<html><body><h1>HTML</h1></body></html>']

re.finadll(r'<.*?>',html)
>>> ['<html>', '<body>', '<h1>', '</h1>', '</body>', '</html>']

로 검색하면, .*표현식이 제일 처음으로 나오는 >에서 멈추는 것이 아니라, 맨 마지막 >까지 검색을 진행한다. *는 0회부터 최대로 패턴이 출력되는 것이니까.

그런데 *이나 +에 최소일치인 ?를 붙여주면, 표현식 다음부분에 해당하는 문자열이 처음나왔을 때부터 그 부분까지만 일치시키고 검색을 마친다.

실습

1.{m}패턴지정자를 사용해서 a로 시작하는 4글자 단어를 전부 찾는다.

re.findall(r'\ba\w{3}\b',story)
>>> ['also', 'able']

2.r로 끝나는 모든 단어를 찾는다.

re.findall(r'\b\w+r\b',story)
>>> ['for', 'daughter', 'clear', 'engineer', 'after', 'over', 'for', 'her', 'her', 'her', 'for', 'her', 'her', 'her', 'favor', 'However', 'for', 'her', 'her', 'her', 'brother', 'her', 'for']

3.a,b,c,d,e중 아무 문자나 3번 연속으로 들어간 단어를 찾는다.

ex) b[eca]me

re.findall(r'\w*[abcde]{3}\w*',story)
re.findall(r'\b\w*[abcde]{3}\w*\b',story)
>>> ['advanced', 'became', 'made', 'embrace', 'became', 'deep']

4.re.sub를 사용해서 ,로 구분된 앞/뒤 단어에 대해 앞단어는 대문자화 시키고, 뒷단어는 대괄호로 감싼다. 이 과정에서, 각각의 앞/뒤에 before, after그룹 이름을 사용한다.

re.findall(r'(\w+)\s*,\s*(\w+)', story)
>>> [('Crownguards', 'the'),
 ('service', 'Luxanna'),
 ('daughter', 'and'),
 ('matured', 'it'),
 ('Somehow', 'she'),
 ('prodigy', 'drawing'),
 ('government', 'military'),
 ('Magic', 'she'),
 ('gift', 'something'),
 ('skills', 'the'),
 ('conflict', 'earning'),
 ('However', 'reconnaissance'),
 ('people', 'Lux'),
 ('Legends', 'where')]

re.sub(r'(\w+)\s*,\s*(\w+)', r'\1[\2]', story)
>>> "Born to the prestigious Crownguards[the] paragon family of Demacian service[Luxanna] was destined for greatness. She grew up as the family's only daughter[and] she immediately took to the advanced education and lavish parties required of families as high profile as the Crownguards. As Lux matured[it] became clear that she was extraordinarily gifted. She could play tricks that made people
...


def repl_function(m):
  return '{} [{}]'.format(m[1].upper(), m[2])

re.sub(r'(\w+)\s*,\s*(\w+)', repl_function ,story)

# 혹은
re.sub(r'(\w+)\s*,\s*(\w+)', (lambda m : '{} [{}]'.format(m[1].upper(), m[2])),story)

# 그룹 지어서 만들기

re.sub(r'(?P<before>\w+)\s*,\s*(?P<after>\w+)',(lambda m : '{} [{}]'.format(m['before'].upper(), m['after']))

>>> "Born to the prestigious CROWNGUARDS [the] paragon family of Demacian SERVICE [Luxanna] was destined for greatness. She grew up as the family's only DAUGHTER [and] she immediately took to the advanced education and lavish parties required of families as high profile as the Crownguards. As Lux MATURED [it] became clear that she was extraordinarily gifted. She could play tricks that made people believe they
...