텍스트를 다루는 일은 프로그래머가 가장 많이 하는 일 중의 하나입니다. 결과적으로 대부분의 언어에 이를 더 쉽게 해주는 수 많은 도구가 있습니다. 이 섹션에서는 몇 가지 도구를 살펴 보겠습니다. 전형적인 프로그래밍 과업을 수행하는데 그런 도구들을 사용해 보겠습니다.
텍스트와 작업할 때 할 수 있는 가장 흔한 과업은 다음과 같습니다:
· 텍스트 줄을 문자 그룹으로 가른다.
· 텍스트 안에서 문자열을 검색한다.
· 텍스트 안에서 문자열을 교체한다.
· 대소문자를 변환한다
이런 과업들을 파이썬을 사용하여 다루어 보겠습니다. 그리고 간략하게 VBScript와 JavaScript가 텍스트를 처리하는 법을 살펴보겠습니다.
파이썬은 2.3 버전 현재 약간 모호한 접근법으로 텍스트를 처리합니다. 이 때문에 초기 파이썬 버전에서는 모든 문자열 조작이 함수와 유용한 상수를 한 가득 갖추고 있는 한 모듈을 통하여 이루어졌습니다. 파이썬 2.0 버전에서 문자열 메쏘드가 도입되었으며 그 모듈에 있는 함수와 중복되어 있습니다. 그러나 상수들은 여전히 그 모듈에 있습니다. 이런 입장은 2.3 버전에도 유지되고 있지만 예전 string 모듈의 필요성을 완전히 제거하는 작업이 물밑에서 진행중입니다. 이번 주제에서는 문자열 조작에 객체 지향적으로 접근하는 법만 살펴 보겠습니다. string 모듈을 시험해 보고 싶으면 편안한 마음으로 파이썬 모듈 문서를 읽어 보세요.
문자열 가르기
생각해 볼 첫 과업은 문자열을 구성요소로 가르는 법입니다. 이는 종종 파일을 처리할 때 필요합니다. 파일을 한줄 한줄 읽어야 할 경우가 많기 때문입니다. 그러나 필요한 데이터 역시 그 줄의 한 조각 안에 포함되어 있을 수 있습니다. 이런 예가 바로 주소록 예제인데, 여기에서는 전체 엔트리를 인쇄하기보다 엔트리의 각 필드마다 따로 접근하고 싶습니다.
이를 위해 사용하는 파이썬 메쏘드는 split()이라고 부릅니다. 다음과 같이 사용됩니다:
>>> aString = "Here is a (short) String"
>>> print aString.split()
['Here', 'is', 'a', '(short)', 'String']
리스트를 돌려받는 것에 주목하세요. 그 안에 aString의 단어들이 공간문자가 모두 제거된 채로 들어 있습니다. ''.split()에 대한 기본 연산자는 공백문자(whitespace (즉, 탭문자와 새줄문자 그리고 공간문자 등등))입니다. 다시 시도해 보고 싶습니다. 그러나 열린 괄호를 가름자로 합니다:
>>> print aString.split('(')
['Here is a ', 'short) String']
차이점을 눈치채셨습니까? 이 번에는 리스트에 오직 원소가 두 개 있을 뿐입니다. 여는 반괄호는 'short)' 앞에서 제거되었습니다. ''.split()에 관하여 기억해야할 중요한 요점은 바로 가름 문자가 제거된다는 것입니다. 보통 그것이 원하는 바이지만, 제거되지 않기를 바랄 경우도 있을 것입니다!
문자열 리스트(연속열)를 받아 원소들을 하나로 묶는 ''.join() 메소드도 있습니다. ''.join()의 애매모호한 특징 하나는 결합 문자로 사용되는 문자열에 메쏘드를 요청한다는 것입니다. 무슨 뜻인지 다음 예제를 보겠습니다:
>>> lst = ['here','is','a','list','of','words']
>>> print '-+-'.join(lst)
here-+-is-+-a-+-list-+-of-+-words
>>> print ' '.join(lst)
here is a list of words
잘 생각해보면 말이 안되는 것은 아니지만, 처음 보면 괴이하게 보입니다.
단어 세기
함수 주제에서 언급했던 단어 세기 프로그램을 다시 연구해 봅시다. 그 의사 코드의 모습을 떠올려 봅시다:
def numwords(aString):
list = split(aString) # 리스트에 단어들이 원소로 담긴다.
return len(list) # 리스트에 있는 원소의 개수를 돌려준다.
for line in file:
total = total + numwords(line) # 각 줄에 대하여 총계를 누적한다.
print "File had %d words" % total
이제 파일에서 줄을 얻는 법을 배웠으므로 numwords() 함수의 몸체를 연구해 봅시다. 먼저 한 줄 안에 든 단어들을 리스트로 만들고 싶습니다. 기본 ''.split() 메쏘드만 적용하면 되는 간단한 일입니다. 파이썬 문서를 들여다보면 내장 len() 함수가 리스트 안의 원소의 개수를 돌려준다는 것을 발견할 수 있습니다. 문자열 안에 든 단어의 개수를 알아야 하는데 - 정확하게 원하는 것이군요.
그래서 최종 코드의 모습은 아래와 같습니다:
def numwords(aString):
lst = aString.split() # split()은 문자열 객체 aString의 메쏘드이다.
return len(lst) # 리스트의 원소 개수를 돌려준다.
inp = file("menu.txt","r")
total = 0 # 0으로 초기화; 또 변수가 생성된다.
for line in inp:
total = total + numwords(line) # 각 줄에 대하여 총계 누적한다.
print "File had %d words" % total
inp.close()
물론 완전하게 옳은 것은 아닙니다. 앰퍼센드 같은 문자도 단어로 세기 때문입니다 (물론 단어라고 생각하실 수도 있겠지만...). 또한, 오직 파일(menu.txt) 하나에만 사용할 수 있습니다. 그러나 사용자에게 말 걸기 섹션에서 보았듯이 명령줄( argv[1])이나 raw_input()을 통하여 파일이름을 읽어 들이도록 변환하는 것이 그렇게 어렵지는 않습니다. 그 일은 독자 여러분에게 과제로 남겨둡니다.
텍스트 검색하기
다음으로 살펴볼 흔한 작업은 긴 문자열 안에서 부분-문자열을 검색하는 일입니다. 이 역시 파이썬 문자열 메쏘드가 지원하는데, 이 번에는 ''.find()라고 부릅니다. 기본적인 사용법은 아주 간단합니다. 검색 문자열을 제공합니다. 그러면 파이썬이 메인 문자열 안에서 그 문자열을 발견하면 첫 문자의 지표를 돌려주고 발견하지 못하면 -1을 돌려줍니다:
>>> aString = "here is a long string with a substring inside it"
>>> print aString.find('long')
10
>>> print aString.find('oxen')
-1
>>> print aString.find('string')
15
앞의 두 예제는 보이는 그대로 이해됩니다. 첫 예제는 'long'이 시작되는 지표를 돌려줍니다. 그리고 두 번째 예제는 'oxen'이 aString 안에서 나타나지 않기 때문에 -1을 돌려줍니다. 세 번째 예제는 흥미로운 점을 제기합니다. 즉, find 메쏘드는 검색 문자열이 처음으로 나타나는 것만 찾습니다. 그러나 검색 문자열이 원래 문자열에서 여러 번 나타나면 어떻게 하는가?
한 가지 방법은 처음 나타난 곳의 지표를 이용하여 원래 문자열을 두 조각으로 가르고 다시 검색하는 것입니다. -1을 결과로 얻을 때까지 계속합니다. 다음과 같이:
aString = "Bow wow says the dog, how many ow's are in this string?"
temp = aString[:] # 사본을 만들려면 조각썰기를 이용한다.
count = 0
index = temp.find('ow')
while index != -1:
count += 1
temp = temp[index + 1:] # 조각썰기
index = temp.find('ow')
print "We found %d occurrences of 'ow' in %s" % (count, aString)
여기에서는 그냥 출현 빈도를 셌지만, 나중에 사용하기 위해 결과 지표들을 리스트에 모을 수도 있습니다.
find() 메쏘드는 추가로 선택적인 매개변수를 사용하면 이 처리 속도를 약간 더 높일 수 있습니다. 즉, 원래 문자열 안의 시작 위치를 지정하는 것입니다:
aString = "Bow wow says the dog, how many o's are in this string?"
count = 0
index = aString.find('ow') # 기본 값으로 시작
while index != -1:
count += 1
start = index + 1
index = aString.find('ow', start) # 새로운 시작 위치 설정
print "We found %d occurrences of 'ow' in %s" % (count, aString)
이 해결책은 새로운 문자열 매번 만들어야 할 필요를 없애줍니다. 매번 문자열을 만들면 문자열이 길 경우 문자열 처리가 아주 느릴 수 있습니다. 또한 부분문자열이 앞에서 몇 문자 안에 있다는 것을 확실하게 알고 있다면 (또는 나중에 나타나는 것은 관심이 없다면) 시작과 중지 값도 지정할 수 있습니다. 다음과 같이:
# 검색을 앞 20 개의 문자로 제한한다
aString = "Bow wow says the dog, how many ow's are in this string?"
print aString.find('ow',0,20)
검색 연구를 완성하기 위해 두 가지 멋진 메쏘드가 더 있습니다. 흔한 검색 상황을 충족시키 위한 것으로 ''.startswith() 메쏘드와 ''.endswith() 메쏘드가 있습니다. 이름만으로도 무엇을 하는 메쏘드인지 짐작이 가실 것입니다. 원래 문자열이 주어진 문자열로 끝나거나 시작하는지에 따라 True나 False를 돌려줍니다. 다음과 같이:
>>> print "Python rocks!".startswith("Perl")
False
>>> print "Python rocks!".startswith('Python')
True
>>> print "Python rocks!".endswith('sucks!')
False
>>> print "Python rocks!".endswith('cks!')
True
불리언 결과에 주목하세요. 무엇보다, 대답이 True라면 어디를 보아야 하는지 이미 알고 있습니다! 또 검색 문자열이 완전한 단어가 되어야 할 필요가 없다는 것을 주목하세요. 부분문자열이면 됩니다. 또한 문자열 안에 start위치와 stop 위치를 지정할 수 있습니다. ''.find()와 똑 같이 문자열 안에 주어진 위치에서 효과적으로 테스트할 수 있습니다. 이는 실용적으로 그렇게 많이 쓰이는 특징은 아닙니다.
마지막으로 또다른 문자열 안에 부문자열이 존재하는지 간단한 테스트를 위하여 파이썬 in 연산자를 이용할 수 있습니다. 다음과 같이:
>>> if 'foo' in 'foobar': print 'True'
True
>>> if 'baz' in 'foobar': print 'True'
>>> if 'bar' in 'foobar': print 'True'
True
이 정도로 문자열 검색에 관한 언급을 마치겠습니다. 다음은 텍스트를 교체하는 법을 살펴봅시다.
텍스트 교체
텍스트를 발견했으면 그것을 다른 것으로 바꾸고 싶은 경우가 종종 있습니다. 역시 파이썬 문자열 메쏘드에서 ''.replace() 메쏘드로 해결책을 제시합니다. 이 메쏘드는 인자를 두 개 받습니다: 검색 문자열과 교체 문자열이 그것입니다. 반환 값은 교체의 결과로 나온 새로운 문자열입니다.
>>> aString = "Mary had a little lamb, its fleece was dirty!"
>>> print aString.replace('dirty','white')
"Mary had a little lamb, its fleece was white!"
''.find()와 ''.replace 사이의 흥미로운 차이점 한 가지는 replace는 달랑 제일 처음 나타나는 것만 교체하는 것이 아니라 나타난 검색 문자열을 모두 교체한다는 것이 기본값입니다. 선택적인 count 인자는 교체의 횟수를 제한할 수 있습니다:
>>> aString = "Bow wow wow said the little dog"
>>> print aString.replace('ow','ark')
Bark wark wark said the little dog
>>> print aString.replace('ow','ark',1) # 오직 하나만 교체한다.
Bark wow wow said the little dog
정규 표현식을 이용하면 훨씬 더 섬세하게 검색과 교체를 할 수 있습니다. 그러나 정규 표현식은 너무 복잡하며 그 자체로 온전한 주제를 차지하므로 이 자습서의 "고급" 섹션에서 다루겠습니다.
대소문자 바꾸기
마지막으로 살펴볼 것은 대문자를 소문자로 그리고 그 반대로 바꾸는 것입니다. 그렇게 흔한 연산은 아니지만 파이썬은 그 일을 도와줄 메쏘드를 제공합니다:
>>> print "MIXed Case".lower()
mixed case
>>> print "MIXed Case".upper()
MIXED CASE
>>> print "MIXed Case".swapcase()
mixED cASE
>>> print "MIXed Case".capitalize()
Mixed case
>>> print "TEST".isupper()
True
>>> print "TEST".islower()
False
''.capitalize()는 전체 문자열을 대문자로 바꾸는 것이 아님에 주목하세요. 전체 문자열에서 첫 문자만 대문자로 바꿉니다. ''.isupper() 그리고 ''.islower()두 가지 테스트 함수(진위함수(predicates))에 주목하세요. 파이썬은 이런 진위 함수를 한 가득 제공합니다. 다른 유용한 진위함수로는 ''.isdigit()와 ''.isalpha() 그리고 ''.isspace()가 있습니다. 마지막 함수는 문자 그대로 공간 문자뿐만 아니라 모든 공백문자를 점검합니다!
자습서를 읽어 가다보면 앞으로 이런 문자열 메쏘드를 많이 사용하게 될 것입니다. 특히 문법 계수기 사례연구에서 많이 사용합니다.
기억해야 할 것 |
· 텍스트 처리는 대부분의 언어가 내장 지원하는 일반적인 연산이다. · 가장 흔한 과업은 텍스틀 가르기와 텍스트 검색과 교체 그리고 대소문자 바꾸기이다. · 언어마다 각자 다른 수준에서 지원하지만 세 가지 기본 연산은 언제나 어디에서나 사용할 수 있다. |
댓글