파이썬(python) – 정규식, 정규표현식(Regular Expression) [두번째]

지난 시간에 정규식의 개념을 잠깐 살펴보았는데요.
이번 시간부터는 python 에서 정규식을 사용하여 문자열 검색을 하는 방법을 실습해보겠습니다.

>>> import re

>>> dir(re)

['DEBUG', 'DOTALL', 'I', 'IGNORECASE', 'L', 'LOCALE', 'M', 'MULTILINE', 'S', 'Scanner', 'T', 'TEMPLATE', 'U', 'UNICODE', 'VERBOSE', 'X', '_MAXCACHE', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', '_alphanum', '_cache', '_cache_repl', '_compile', '_compile_repl', '_expand', '_pattern_type', '_pickle', '_subx', 'compile', 'copy_reg', 'error', 'escape', 'findall', 'finditer', 'match', 'purge', 'search', 'split', 'sre_compile', 'sre_parse', 'sub', 'subn', 'sys', 'template']

>>> 

re 모듈에서 제공하는 함수 중에서 가장 많이 사용하는 3 가지를 이용해서 실습을 해보겠습니다.

  1. search : string 전체를 검색하여 match 되는 첫번째 문자열을 찾음.
    <match object> = re.search( pattern, string, flag=0)
  2. match : string 의 첫 문자열만을 비교하여 pattern 과 match 되는지를 확인.
    <match object> = re.match( pattern, string, flag=0)
  3. findall : string 전체에서 pattern 과 일치하는 것을 모두 찾아서 list 로 리턴해줌.
    [list of groups, list of tuples] = re.findall( pattern, string, flag=0 )

위의 3 함수 모두 인자의 형태는 같습니다. 정규식을 나타내는 pattern 과 원본 string, 그리고 option flag 입니다.
중요한 것은 리턴형인데 이 리턴형을 정확히 알고 있어야만 해당 결과를 이용하여 또 다른 작업을 할 수 있기 때문입니다. 예를 들어 re.search 의 리턴형이 match object 라는 것을 모른 채 마치 string 처럼 취급해서 연산을 하려고 하면 계속해서 오류를 만날겁니다.
match object 는 객체이니만큼 당연히 해당 객체가 제공하는 함수들을 이용하여 연산 및 작업을 해야 합니다.

먼저 간단히 re.search 를 이용해서 다양한 문자열 검색을 시험해볼까요 ?
첫번째 인자로 주어지는 pattern 에는 이전 시간에 배운 정규식 표현들이 사용될 수 있습니다. 정규식 표현들은 여러분이 머리속에 많이 알고 있을수록 상황에 맞게 좀더 예쁜 pattern 을 만들어낼 수 있습니다.
사실 저는 머리가 나빠서 몇 가지밖에 기억하지 못하고 나머지는 찾아보는 편입니다. ^^

# re 모듈 import
>>> import re                                              

# search 를 이용하여 iig 문자열 검색
>>> match = re.search('iig', 'called piig');     

# search 결과 match object 의 group 함수 이용
>>> match.group()
'iig'

# search 의 return 형이 match object 임
>>> type(match)
<type '_sre.SRE_Match'>
>>> match
<_sre.SRE_Match object at 0x2b26d13dcbf8>

# 없는 문자열 검색
>>> match = re.search('igs', 'called piig');
>>> match.group()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

# 검색 후 return 형을 확인해보니 NoneType
>>> type(match)
<type 'NoneType'>  # 이는 검색 결과가 없다는 것임.

# . 은 하나의 문자를 가리킴. 즉 앞에 아무문자나
>>> match = re.search('..g', 'called piig');    

# 두문자가 온 후 그 다음 문자가  g 인 것을 검색
>>> match.group()                                      
'iig'

# i 와 g 사이에 문자 두개가 있는 것을 검색
>>> match = re.search('i..g', 'called piig');    

# 결과가 없는 것임. NoneType
>>> match.group()                                

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

# p 와 g 사이 문자 두개
>>> match = re.search('p..g', 'called piig');
>>> match.group()
'piig'

# a 와 c 사이에 한문자가 있는 문자열 검색
>>> match = re.search('a.c', 'a.c called piig');
>>> match.group()
'a.c'

# a 와 c 사이 .(점) 문자가 있는 문자열 검색
>>> match = re.search('a.c', 'a.c called piig');
>>> match.group()
'a.c'

# a 와 c 사이에 한문자가 있는 문자열 검색
>>> match = re.search('a.c', 'abc called piig');     

>>> match.group()
'abc'

# a.c 라는 문자열 검색 (. 은 . 문자)
>>> match = re.search('a.c', 'abc called piig');
>>> match.group()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

# : 문자 뒤에 두개의 아무문자나
>>> match = re.search(':ww', 'abc called :piig');
>>> match.group()
':pi'

# : 문자뒤에 아무문자나 1 개 이상 붙은것
>>> match = re.search(':[w]+', 'abc called :piig');
>>> match.group()
':piig'

위의 실습에서 match.group() 이라는 함수를 수행하면 'iig' 라는 문자열을 반환하는 예를 보셨을 겁니다.
그런데, 이런 문자열을 반환하는 함수 이름을 왜 group() 이라고 지었을까요?
궁금하지 않으세요 ? 전 처음에는 그게 궁금하더라구요.
그래서, 좀 찾아봤더니 search 나 match 을 이용하여 검색할 때 첫번째 인자인 pattern 을 여러개를 줄 수 있어서, 결과도 하나의 string 이 아닌 group(tuple) 으로 리턴할 수 있기 때문에 그렇습니다.

다음의 예를 한번 보시죠.
pattern 을 하나하나 소괄호'()'로 묶어서 search 의 인자로 제공했습니다. 첫번째 검색 pattern 은 (ddd) 이고 두번째 검색 pattern 은 (www)입니다.
이렇게 검색하려는 pattern 을 group 으로 한번에 여러 개를 제공한 것입니다.
그러니, 결과도 group 으로 나와야 겠지요. 즉, tuple 로 결과를 묶어서 리턴합니다.

# 2 개의 pattern group 검색
>>> match = re.search('(ddd)-(www)', '123-abc');  

# 결과로 리턴된 group 을 tuple 로 묶어서 보여줌.
>>> match.groups()
('123', 'abc')

# 결과 리턴된 각 group 의 string 을 연결해서 보여줌
>>> match.group()
'123-abc'

# 0 번째 group 은 없고 match.group() 과 같은 결과
>>> match.group(0)
'123-abc'

# 첫번째 group (element)을 string 으로 리턴
>>> match.group(1)
'123'

# 두번째 group (element)을 string 으로 리턴
>>> match.group(2)
'abc'

# 하나의 pattern group 만으로 검색
>>> match = re.search('(ab)','ab');          

# tuple 의 element 는 하나임
>>> match.groups()
('ab',)
>>> match.group(1)
'ab'
>>> match.group(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: no such group

# 3 개의 group 검색
>>> match = re.search('(aaa).(bbb)@(cc.)', '  aaa.bbb@ccx');
>>> match.groups()
('aaa', 'bbb', 'ccx')

정규식도 배웠는데 조금만 더 복잡한 것을 시험해볼까요 ?

>>> m = re.search('([w.]+)@([w.]+)', 'blah dp.lee.dp.lee@gmail.com yyxdf @ aa@xx')
>>> m.groups()
('dp.lee.dp.lee', 'gmail.com')
>>> m.group(1)
'dp.lee.dp.lee'
>>> m.group(2)
'gmail.com'

위에서는 두 개의 group pattern 을 제공했는데, 첫번째와 두번째 모두 ([w.]+) 형태로 찾는 것이네요.
이 정규식의 의미는 [ http://kio.zc.bz/Lecture/regexp.html ] 에서 다시 좀 찾아보신 후 의미를 파악해 주세요.

이상 search() 함수를 통해 문자열 검색을 시험해 보았는데요.
match() 함수는 pattern 문자열을 검색 대상 문자열의 맨 첫 문자열과 비교하여 pattern 이 일치하는지를 검사합니다. 일치하지 않으면 NoneType 리턴입니다.
이 점만 search() 함수와 다르고 나머지는 모두 같습니다.

>>> import re

# 첫 문자가 안맞으면 false
>>> bool(re.match('[0-9]', '    77'))
False

# 문자열 전체 검색이므로 있으면 true
>>> bool(re.search('[0-9]', '    77'))                   
True

findall() 함수는 search, match 함수와는 다르게 모든 문자열을 검색하여 match 되는 모든 것을 List 로 리턴한다는 점이 다릅니다.

다음의 예를 보실까요 ?

>>> m = re.findall('([w.]+)@([w.]+)', 'blah dp.leedp.lee@gmail.com yyxdf @ aa.xx@xx.net')

# 결과는 match object 가 아니기 때문에
>>> m.groups()
Traceback (most recent call last):   # groups 나 group 같은 함수가 없음.
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'groups'
>>> m.group()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'group'

# 결과는 각 pattern group 결과(tuple)가 List 로 묶여진 것임.
# 이후 m 은 List 연산들을 자유롭게 수행.

>>> m
[('dp.leedp.lee', 'gmail.com'), ('aa.xx', 'xx.net')]  

이제 정규식을 이용하여 문자열 검색을 수행하는 방법을 거의 알아본 것 같습니다.
이번 강의는 좀 길었네요.

그런데, 생각해보니 아직 한가지 알려드리지 않은 것이 또 남아있었네요. ㅎㅎ
바로 위의 3 함수의 세번째 인자인 option flag 에 대해 언급해 드리지를 않았네요.

option flag 는 여러분이 부여하지 않아도 default 가 지정되어 있습니다.
하지만, 특수한 경우에는 대소문자 구분을 해서 찾아라... 등 option 을 주면 편리한 경우들이 있습니다.
사용예를 하나만 보여드릴께요. flag 를 여러개 조합해서 부여할 때는 '|' 를 사용하면 됩니다.
즉, re.I | re.M | re.S 이런 방식으로 사용하시면 됩니다.

# default option 으로 사용하면
>>> m = re.search('AA', '  aa bb cc')                    

# 대소문자 구분해서 아무것도 못찾음
>>> m.group()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

# re.I 옵션을 사용하니 대소문자 미구분
>>> m = re.search('AA', '  aa bb cc', re.I)            

# case-insensitive 라고 하죠.
>>> m.group()
'aa'

아래의 표는 사용할 수 있는 option flag 에 대한 간단한 영문 설명입니다. 해석은 각자의 몫으로... ^^
사용할 수 있는 option flag 의 list 를 보고 싶으시면 dir(re)를 하시면 symbol list 가 나올텐데 유심히 보시면 아래의 목록이 모두 나올 것입니다.
항상 책이나 인터넷을 찾기 전에 dir, help 를 애용하는 것 잊지 마세요. ^^

Modifier Description
re.I Performs case-insensitive matching.
re.L Interprets words according to the current locale.This interpretation affects the alphabetic group (w and W), as well as word boundary behavior (b and B).
re.M Makes $ match the end of a line (not just the end of the string) and makes ^ match the start of any line (not just the start of the string).
re.S Makes a period (dot) match any character, including a newline.
re.U Interprets letters according to the Unicode character set. This flag
affects the behavior of w, W, b, B.
re.X Permits "cuter" regular expression syntax. It ignores whitespace (except inside a set [] or when escaped by a backslash), and treats unescaped # as a comment marker.

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x