본문 바로가기
개인 공부/rookies

정규 표현식

by 아메리카노와떡볶이 2022. 8. 30.
728x90
정규 표현식 ( regular expression )

정규 표현식에 대해 알아보자.

 

먼저 정규표현식은 특정한 규칙을 가진 문자열의 집합을 표현할때 사용하는 언어이다.

보통 웹사이트에 회원가입할 때를 떠올려보면 주민등록번호, 이메일, 휴대폰 번호,비밀번호  등에 대한 형식 검사가 이루어진다. 이때 사용되는 것이 정규 표현식이다.

 

구체적으로 예시를 들면 비밀번호의 안전성을 높이기 위해 특수문자를 포함하는 비밀번호를 설정해야하는데 이런 것도

정규표현식을 통해 구현할 수 있다.

기본적으로는 re 모듈을 import해서 정규 표현식의 규칙을 지정하고 사용한다.

import re
pattern = re.compile('[a]')
print(pattern.search('abcdefg'))

위의 예시에서는 pattern에 정규 표현식에 문자 a를 설정했다.

따라서 pattern.search는 문자열 abcdefg 중에서 a를 찾게되는 것이다.

 

정규 표현식 관련 메소드

위의 예시에서는 search 메소드가 사용되었는데 이 외에도 자주 사용되는 메소드들이 있다.

메소드 설명
match()  문자열의 처음부터 정규식과 매치되는지 확인
매치될 때 match 객체 반환
매치되지 않으면 None 반환
search()  문자열 전체를 검색하여 정규식과 매치되는지 확인
매치될 때 match 객체 반환
매치되지 않으면 None 반환
findall()  정규식과 매치되는 모든 문자열을 List로 반환
finditer() 
정규식과 매치되는 모든 문자열을 iterator 객체로 반환

메소드들의 반환형을 잘 숙지하고 있어야 데이터를 관리하기 수월하다.

정규 표현식 관련 기호

다음은 정규식에서 사용되는 기호를 알아보자.

 

1. 대괄호 []

대괄호사이에 문자를 입력하는 방식으로 사용되고 or 의미로 적용된다.두 문자 사이에 하이픈(-)을 사용하면 범위(from - to)를 나타낸다

 

예를 들어pattern = re.compile([abc]) 라고 정규식을 설정한다면 a or b or c 가 된다.따라서 위의 정규식을 가지고 몇가지 문자열에 적용해보면 아래와 같다.

"a" : a가 있으므로 매치

"black" : b, a 가 있으므로 매치

"desk" : a,b,c가 없으므로 매치 X

 

또 알아야할 것은 하이픈이 범위를 나타낸다는 것이다.

[a-z] 는 a부터 z까지 알파벳을 나타내고, [0-9]는 0부터 9까지의 숫자, [ㄱ-힣]은 모든 한글을 나타낸다.

 

2. 캐럿 ^

대괄호안에 ^ 문자가 사용되면 부정을 의미한다.

즉 [^abc]는 a or b or c 의 부정이므로, a,b,c가 아닌 문자를 의미한다.

 

3. Dot .

줄바꿈 문자를 제외한 모든 문자와 매치

즉 a.b 라고 쓰면 

aab, abb, a가b, a(b 다 match

 

호안에 ^ 문자가 사용되면 부정을 의미한다.

즉 [^abc]는 a or b or c 의 부정이므로, a,b,c가 아닌 문자를 의미한다.

 

4. Asterisk  *

바로 앞의 문자가 0 ~ N 번 등장하는 경우 match

a*b 라고 정규식을 쓸 경우

b 앞에 * 앞의 문자 a가 0 ~ N번 나오는 경우 다 match 된다. 

예를 들어 b, ab, aab, aaab 다 가능

단 acb 의 경우 b앞에 a가 아닌 c가 있으므로 None

 

'+' 는 이와 유사한 문자로, 바로 앞의 문자가 1 ~ N번 등장하는 경우 해당됨

 

 

 

즉 a.b 라고 쓰면 

aab, abb, a가b, a(b 다 match

 

호안에 ^ 문자가 사용되면 부정을 의미한다.

즉 [^abc]는 a or b or c 의 부정이므로, a,b,c가 아닌 문자를 의미한다.

 

3. 특수문자

여기까지 살펴보면 알겠지만, 자주 사용되는 표현이 정해져있다. 대부분의 텍스트는 문자와 숫자 등으로 이루어져있기때문에 [0-9], [a-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ㄱ-힣]와 동일

이 중에 \d와 \w는 정말 많이 쓰이기때문에 꼭 기억하자.

 

4. 연습문제

여기까지 배운 내용으로 연습문제를 해결해보자.

웹에서 회원가입 페이지를 구현하려는데 사용자 아이디의 유효성 검사조건을 구현해야한다.

 

구현하려는 조건은 아래와 같다.

1. 아이디의 시작 문자는 영어로 설정해야한다.

2. 아이디는 영어와 숫자로만 작성되어야한다.

import re
pattern1 = re.compile('[a-zA-Z]') # 소문자 a~z, 대문자 A-Z 를 허용하는 정규식
pattern2 = re.compile('[a-zA-Z0-9]') # 영어와 숫자만 허용

id = 'seongyeob1234'

result1 = pattern1.match(id)
result2 = pattern2.search(id)

if result1 and result2:
           print('%s <- 가입 가능' % id)
else:
           print('%s <- 가입 불가' % id)

문자열의 시작 문자 검사는 match를 통해 하면 된다.

즉 pattern1.match를 통해 시작문자가 영어인지 검사하는 것이다.

 

다음으로, search를 통해 전체 아이디가 a~z, A~Z, 0~9 로만 이루어져있는지 검사한다.

 

둘 중 하나라도 매치되지 않으면 None 값을 가지게 될 것이다.

따라서 조건을 result1 and result2 로 설정해주면 유효성 검사 구현이 완료된다.

 

다음은 은행 계좌번호 형식을 검사하려고 한다.

 

1. 1oo-1o-oooooo

2. 계좌번호의 처음과 두번째는 1로 시작하고, 마지막은 6자리 숫자로 이루어져있다.

 

계좌번호의  경우 0~9 까지 숫자로만 이루어져있기때문에 [0-9] 로 정규식을 표현할 수 있다.

하지만 [0-9] 를 나타내는 특수문자인 \d를 활용해보자.

import re
pattern = re.compile('1\d{2}-1\d{1}-\d{6}')

text = '123-12-123456'

print(text, end =' <- ')

res = pattern.search(text)

if res:
   print('pass')
else:
   print('fail')

정규식을 살펴보면 '1\d{2}-1\d{1}-\d{6}' 과 같이 작성되었다.

숫자 1과 하이픈(-)은 고정값이기때문에 정규식에 넣어주고, 0~9 사이 임의의 숫자의 개수를

{ } 를 통해 작성해주면 된다.

 

마지막으로 주민등록번호의 유효성을 검사해보자.

주민등록번호는 고정된 형식을 가지고 있다.

1. 생년월일 6글자와 뒷자리 7자

2. 뒷자리의 처음 숫자는 1~4 사이의 숫자.

import re
pattern = re.compile('\d{6}-[1-4]{1}\d{6}')

text = '791111-5234567'

print(text, end =' <- ')

res = pattern.search(text)

if res:
   print('pass')
else:
   print('fail')

위와 같은 방식으로 정규식을 작성하면 된다. 이때 뒷자리의 처음 숫자는 1~4 의 숫자이기때문에 [1-4]{1} 로 작성했다.

 

정규식 그룹화

말 그대로 정규식에 그룹을 넣는 것이다.

import re
pattern = re.compile('(\d{3})-(\d{4})-(\d{4})')
result = pattern.search('010-1234-5678')
print(result.group())
print(result.group(0))
print(result.group(1))
print(result.group(2))
print(result.group(3))

출력결과는 위와 같다. 즉 정규식내에서 그룹을 만들면 첫번째부터 인덱스 1,2,3 이렇게 그룹번호가 주어진다.

그룹화를 통해서 원하는 부분만 묶어서 출력할 수 있다.

 

한번 예제에 적용해보자

import re

pattern = re.compile(
    '이름\s*:\s*(\w+).*전화번호\s*:\s*(\d+)'
)

text = '''
사용자 정보, 이름: 성엽, 전화번호 : 1234, 이메일 : seongyeob@mail.com
'''

# 코드 작성
result = pattern.search(text)
result.group(1), result.group(2)

정규식을 살펴보면 '이름\s*:\s*(\w+).*전화번호\s*:\s*(\d+)' 로 표현되어있다. 복잡해보이지만 하나씩 살펴보면 쉽다.

먼저 \s*는 공백을 체크하는 것인데, *를 통해 0~N 개의 공백이 있더라도 match 하게 된다.

첫번째 그룹은 (\w+) 즉 문자+숫자를 match하는것이다. 그리고 dot 을 통해 콤마를 match 해준다.

 

전화번호도 같은 방식으로 표현되어있다. 두번째 그룹은 (\d+)을 통해 숫자를 match하고 있다.

 

따라서 출력하면 group1에는 성엽이, group2에는 1234가 출력된다.

 

간단한 과제 풀이1

 

과제풀이2

728x90

댓글