제 3 장 객체지향 작업틀 | 목 차 | 제 5 장 유닛 테스트 >> |
가끔 comp.lang.python에서 다음과 같은 질문들을 본다 “어떻게 HTML문서에서 모든 [headers|images|links]를 나열할 수 있는가?” “어떻게 태그들을 그대로 두고서 HTML 문서의 텍스트를 [parse|translate|munge] 할 수 있는가?” “어떻게 HTML 태그 속성들을 단 번에 [add|remove|quote] 할 수 있는가?” 이 장은 이러한 모든 질문들에 답해 줄것이다.
여기에 완전한 작동하는 프로그램이 두 부분으로 나뉘어 있다. 첫 번째 부분은 BaseHTMLProcessor.py인데, 이는 태그와 텍스트 블록을 훓어 봄으로써 HTML 파일을 처리하도록 도와 주는 범용적인 도구이다. 두 번째 부분은 dialect.py인데, 이는 태그는 그대로 두고서, HTML 문서의 텍스트를 번역하기 위하여 BaseHTMLProcessor.py를 사용하는 방법을 보여주는 예제이다. 문서화 문자열(doc string)과 주석을 읽고서 무엇이 진행되고 있는지 대충 살펴 보라. 대부분은 신비스런 마술과 같이 보일 것이다. 왜냐하면 이러한 클래스 모두가 도대체 어떻게 호출됐는지 알 수 없기 때문이다. 걱정하지 마라. 모든 신비는 때가 되면 드러나게 될 것이다.
Example 4.1. BaseHTMLProcessor.py
아직 그렇게 하지 못했다면, 이 예제와 더불어 이 책에서 사용된 다른 예제들을 내려 받을 수 있다 (Windows, UNIX, Mac OS).
from sgmllib import SGMLParser class BaseHTMLProcessor(SGMLParser): def reset(self): # extend (called by SGMLParser.__init__) self.pieces = [] SGMLParser.reset(self) def unknown_starttag(self, tag, attrs): # called for each start tag # attrs is a list of (attr, value) tuples # e.g. for <pre class="screen">, tag="pre", attrs=[("class", "screen")] # Ideally we would like to reconstruct original tag and attributes, but # we may end up quoting attribute values that weren't quoted in the source # document, or we may change the type of quotes around the attribute value # (single to double quotes). # Note that improperly embedded non-HTML code (like client-side Javascript) # may be parsed incorrectly by the ancestor, causing runtime script errors. # All non-HTML code must be enclosed in HTML comment tags (<!-- code -->) # to ensure that it will pass through this parser unaltered (in handle_comment). strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) def unknown_endtag(self, tag): # called for each end tag, e.g. for </pre>, tag will be "pre" # Reconstruct the original end tag. self.pieces.append("</%(tag)s>" % locals()) def handle_charref(self, ref): # called for each character reference, e.g. for " ", ref will be "160" # Reconstruct the original character reference. self.pieces.append("&#%(ref)s;" % locals()) def handle_entityref(self, ref): # called for each entity reference, e.g. for "©", ref will be "copy" # Reconstruct the original entity reference. self.pieces.append("&%(ref)s;" % locals()) def handle_data(self, text): # called for each block of plain text, i.e. outside of any tag and # not containing any character or entity references # Store the original text verbatim. self.pieces.append(text) def handle_comment(self, text): # called for each HTML comment, e.g. <!-- insert Javascript code here --> # Reconstruct the original comment. # It is especially important that the source document enclose client-side # code (like Javascript) within comments so it can pass through this # processor undisturbed; see comments in unknown_starttag for details. self.pieces.append("<!--%(text)s-->" % locals()) def handle_pi(self, text): # called for each processing instruction, e.g. <?instruction> # Reconstruct original processing instruction. self.pieces.append("<?%(text)s>" % locals()) def handle_decl(self, text): # called for the DOCTYPE, if present, e.g. # <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" # "http://www.w3.org/TR/html4/loose.dtd"> # Reconstruct original DOCTYPE self.pieces.append("<!%(text)s>" % locals()) def output(self): """Return processed HTML as a single string""" return "".join(self.pieces)
import re from BaseHTMLProcessor import BaseHTMLProcessor class Dialectizer(BaseHTMLProcessor): subs = () def reset(self): # extend (called from __init__ in ancestor) # Reset all data attributes self.verbatim = 0 BaseHTMLProcessor.reset(self) def start_pre(self, attrs): # called for every <pre> tag in HTML source # Increment verbatim mode count, then handle tag like normal self.verbatim += 1 self.unknown_starttag("pre", attrs) def end_pre(self): # called for every </pre> tag in HTML source # Decrement verbatim mode count self.unknown_endtag("pre") self.verbatim -= 1 def handle_data(self, text): # override # called for every block of text in HTML source # If in verbatim mode, save text unaltered; # otherwise process the text with a series of substitutions self.pieces.append(self.verbatim and text or self.process(text)) def process(self, text): # called from handle_data # Process text block by performing series of regular expression # substitutions (actual substitions are defined in descendant) for fromPattern, toPattern in self.subs: text = re.sub(fromPattern, toPattern, text) return text class ChefDialectizer(Dialectizer): """convert HTML to Swedish Chef-speak based on the classic chef.x, copyright (c) 1992, 1993 John Hagerman """ subs = ((r'a([nu])', r'u\1'), (r'A([nu])', r'U\1'), (r'a\B', r'e'), (r'A\B', r'E'), (r'en\b', r'ee'), (r'\Bew', r'oo'), (r'\Be\b', r'e-a'), (r'\be', r'i'), (r'\bE', r'I'), (r'\Bf', r'ff'), (r'\Bir', r'ur'), (r'(\w*?)i(\w*?)$', r'\1ee\2'), (r'\bow', r'oo'), (r'\bo', r'oo'), (r'\bO', r'Oo'), (r'the', r'zee'), (r'The', r'Zee'), (r'th\b', r't'), (r'\Btion', r'shun'), (r'\Bu', r'oo'), (r'\BU', r'Oo'), (r'v', r'f'), (r'V', r'F'), (r'w', r'w'), (r'W', r'W'), (r'([a-z])[.]', r'\1. Bork Bork Bork!')) class FuddDialectizer(Dialectizer): """convert HTML to Elmer Fudd-speak""" subs = ((r'[rl]', r'w'), (r'qu', r'qw'), (r'th\b', r'f'), (r'th', r'd'), (r'n[.]', r'n, uh-hah-hah-hah.')) class OldeDialectizer(Dialectizer): """convert HTML to mock Middle English""" subs = ((r'i([bcdfghjklmnpqrstvwxyz])e\b', r'y\1'), (r'i([bcdfghjklmnpqrstvwxyz])e', r'y\1\1e'), (r'ick\b', r'yk'), (r'ia([bcdfghjklmnpqrstvwxyz])', r'e\1e'), (r'e[ea]([bcdfghjklmnpqrstvwxyz])', r'e\1e'), (r'([bcdfghjklmnpqrstvwxyz])y', r'\1ee'), (r'([bcdfghjklmnpqrstvwxyz])er', r'\1re'), (r'([aeiou])re\b', r'\1r'), (r'ia([bcdfghjklmnpqrstvwxyz])', r'i\1e'), (r'tion\b', r'cioun'), (r'ion\b', r'ioun'), (r'aid', r'ayde'), (r'ai', r'ey'), (r'ay\b', r'y'), (r'ay', r'ey'), (r'ant', r'aunt'), (r'ea', r'ee'), (r'oa', r'oo'), (r'ue', r'e'), (r'oe', r'o'), (r'ou', r'ow'), (r'ow', r'ou'), (r'\bhe', r'hi'), (r've\b', r'veth'), (r'se\b', r'e'), (r"'s\b", r'es'), (r'ic\b', r'ick'), (r'ics\b', r'icc'), (r'ical\b', r'ick'), (r'tle\b', r'til'), (r'll\b', r'l'), (r'ould\b', r'olde'), (r'own\b', r'oune'), (r'un\b', r'onne'), (r'rry\b', r'rye'), (r'est\b', r'este'), (r'pt\b', r'pte'), (r'th\b', r'the'), (r'ch\b', r'che'), (r'ss\b', r'sse'), (r'([wybdp])\b', r'\1e'), (r'([rnt])\b', r'\1\1e'), (r'from', r'fro'), (r'when', r'whan')) def translate(url, dialect="chef"): """fetch URL and translate using dialect dialect in ("chef", "fudd", "olde")""" import urllib sock = urllib.urlopen(url) htmlSource = sock.read() sock.close() parserName = "%sDialectizer" % dialect.capitalize() parserClass = globals()[parserName] parser = parserClass() parser.feed(htmlSource) parser.close() return parser.output() def test(url): """test all dialects against URL""" for dialect in ("chef", "fudd", "olde"): outfile = "%s.html" % dialect fsock = open(outfile, "wb") fsock.write(translate(url, dialect)) fsock.close() import webbrowser webbrowser.open_new(outfile) if __name__ == "__main__": test("http://diveintopython.org/odbchelper_list.html")
Example 4.3. Output of dialect.py
이 스크립트를 실행하면 Lists 101을 (The Muppets(손인형극)에서 나오는) Swedish Chef의-말투로, (벅스 버니 만화영화에 나오는) Elmer Fudd-말투로 , 그리고 (초서의 캔터베리 이야기에 대충 기초한) 중세 영어풍으로 번역할 것이다. 출력 페이지의 HTML 소스를 살펴보면, 모든 HTML 태그와 속성들은 손대지 않았으나, 태그들 사이의 텍스트는 그 흉내 언어로 “번역된” 것을 볼수 있을 것이다. 더 가까이 살펴보면, 사실 타이틀과 문단만이 번역된 것을 볼 것이다; 코드 목록과 화면에 나오는 예제들은 손대지 않은 채 그대로 있다.
HTML 처리는 세 단계로 나뉜다: HTML을 그 구성 조각으로 나누기, 그 조각을 조작하기, 그리고 그 조각을 다시 HTML 로 재구성하기. 첫 단계는 표준 파이썬 라이브러리에 있는 sgmllib.py에 의해서 수행된다.
sgmllib.py에는 중요한 클래스가 하나 포함되어 있다: SGMLParser가 그것으로서, 이 문맥에서 해석기(parser)는 구조화되어 있지 않은 복잡한 개체를 더 단순한 구조화된 개체들로 조각내는 단순한 코드이다. 여러 종류의 해석기들이 있다; 파이썬의 표준 라이브러리에는 명령어 줄 선택사항, .INI 파일, 전자 우편함, robots.txt 파일, XML 등등을 해석하기 위한 모듈들이 있다.
어떤 해석기들은 상태를 유지한다. 즉, 데이타를 해석하고 그것을 내부에 구조적 형식으로 저장한 다음, 나중에 누군가 그 구조화된 데이타를 사용하기를 기다린다. SGMLParser는 자신이 해석한 데이타를 저장하지 않는다; 대신에, 어떤 데이타를 유용한 조각으로 분해하는 데 성공하자 마자, 무엇이 발견 되었는가에 따라서 자신에게 있는 메쏘드를 호출한다. 이 해석기를 사용하려면 SGMLParser 클래스를 하부클래스화하고 이러한 메쏘드를 덮어쓰면 된다.
SGMLParser는 HTML을 8 가지의 데이타로 해석하고, 그 데이터 각각에 대하여 따로따로 메쏘드를 호출한다:
![]() | |
파이썬 2.0에는 버그가 있어서 SGMLParser는 선언을 전혀 인식하지 못한다 (handle_decl이 절대로 호출되지 않을 것이다). 그것은 DOCTYPE이 조용히 무시될 것이라는 것을 뜻한다. 파이썬 2.1에서는 수정되었다. |
sgmllib.py에는 이것을 예시해주는 테스트 모둠이 따라온다. sgmllib.py을 실행할 수 있는데, 명령어 줄에 HTML 파일의 이름을 넘겨 주면, 해석을 하면서 태그 그리고 기타 요소들을 출력할 것이다. SGMLParser 클래스를 하부클래스화하고 그리고 unknown_starttag, unknown_endtag, handle_data 그리고 그들의 인자를 단순히 출력하는 다른 메쏘드들을 정의하면 수행된다.
![]() | |
윈도우즈의 파이썬 IDE라면 “Run script” 대화상자에서 명령어 줄 인자들을 지정할 수 있다. |
Example 4.4. Sample test of sgmllib.py
다음은 이 책의 HTML 버전 toc.html에 있는 목록에서 얻어온 작은 조각이다.
<h1> <a name='c40a'></a> Dive Into Python </h1> <p class='pubdate'> 28 Feb 2001 </p> <p class='copyright'> Copyright copy 2000, 2001 by <a href='mailto:f8dy@diveintopython.org' title='send e-mail to the author'> Mark Pilgrim </a> </p> <p> <a name='c40ab2b4'></a> <b></b> </p> <p> This book lives at <a href='http://diveintopython.org/'> http://diveintopython.org/ </a> . If you're reading it somewhere else, you may not have the latest version. </p>
이것을 sgmllib.py의 테스트 모둠을 통하여 실행하면 다음 결과가 산출된다:
start tag: <h1>
start tag: <a name="c40a" >
end tag: </a>
data: 'Dive Into Python'
end tag: </h1>
start tag: <p class="pubdate" >
data: '28 Feb 2001'
end tag: </p>
start tag: <p class="copyright" >
data: 'Copyright '
*** unknown entity ref: ©
data: ' 2000, 2001 by '
start tag: <a href="mailto:f8dy@diveintopython.org" title="send e-mail to the author" >
data: 'Mark Pilgrim'
end tag: </a>
end tag: </p>
start tag: <p>
start tag: <a name="c40ab2b4" >
end tag: </a>
start tag: <b>
end tag: </b>
end tag: </p>
start tag: <p>
data: 'This book lives at '
start tag: <a href="http://diveintopython.org/" >
data: 'http://diveintopython.org/'
end tag: </a>
data: ".\012If you're reading it somewhere else, you may not have the lates"
data: 't version.\012'
end tag: </p>
다음은 이장의 나머지에서 다룰 지도이다:
HTML 문서로부터 데이타를 추출하려면 SGMLParser 클래스를 하부클래스화하고 얻고자 하는 개체 또는 각 태그를 위한 메쏘드를 정의하라.
HTML 문서로부터 데이타를 추출하기 위한 첫 단계는 약간의 HTML 파일을 얻는 것이다. 하드 디스크에 약간의 HTML이 있다면, 파일 함수들을 사용하여 읽을 수 있지만, 진짜 재미는 생생하게 살아 있는 웹 페이지로부터 HTML을 얻을 때 시작된다.
Example 4.5. Introducing urllib
>>> import urllib>>> sock = urllib.urlopen("http://diveintopython.org/")
>>> htmlSource = sock.read()
>>> sock.close()
>>> print htmlSource
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head> <meta http-equiv='Content-Type' content='text/html; charset=ISO-8859-1'> <title>Dive Into Python</title> <link rel='stylesheet' href='diveintopython.css' type='text/css'> <link rev='made' href='mailto:f8dy@diveintopython.org'> <meta name='keywords' content='Python, Dive Into Python, tutorial, object-oriented, programming, documentation, book, free'> <meta name='description' content='a free Python tutorial for experienced programmers'> </head> <body bgcolor='white' text='black' link='#0000FF' vlink='#840084' alink='#0000FF'> <table cellpadding='0' cellspacing='0' border='0' width='100%'> <tr><td class='header' width='1%' valign='top'>diveintopython.org</td> <td width='99%' align='right'><hr size='1' noshade></td></tr> <tr><td class='tagline' colspan='2'>Python for experienced programmers</td></tr> [...snip...]
![]() | urllib 모듈은 표준 파이썬 라이브러리에 포함되어 있다. 이 모듈은 인터넷-기반 URL로부터 (주로 웹페이지로부터) 데이타를 실제로 검색하고 정보를 획득하는 함수들을 가진다. |
![]() | urllib를 가장 손쉽게 사용하는 방법은 urlopen 함수를 사용하여 웹페이지의 전체 텍스트를 검색하는 것이다. URL을 여는 것은 파일을 여는 것과 비슷하다. urlopen의 반환값은 파일-비슷한 객체로서, 파일 객체와 똑 같은 메쏘드를 가진다. |
![]() | urlopen이 돌려준 파일-비슷한 객체로 할 수 있는 가장 간단한 일은 읽기(read)이다. 이 객체는 웹 페이지 전체 HTML을 한개의 문자열로 읽어 들인다. 그 객체는 또한 readlines을 지원하는데, 이 메쏘드는 텍스트를 한줄한줄 리스트로 읽어 들인다. |
![]() | 이 객체로 일을 마치면, 정상적인 파일 객체와 똑 같이 확실하게 닫아라. |
![]() | 이제 http://diveintopython.org/ 홈페이지의 완전한 HTML을 하나의 문자열로 가지고 있으며, 해석할 준비가 되었다. |
Example 4.6. Introducing urllister.py
아직 그렇게 하지 못하였다면, 이 예제와 함께 이 책에서 사용되는 다른 예제들을 내려 받을 수 있다(Windows, UNIX, Mac OS).
from sgmllib import SGMLParser class URLLister(SGMLParser): def reset(self):SGMLParser.reset(self) self.urls = [] def start_a(self, attrs):
href = [v for k, v in attrs if k=='href']
![]()
if href: self.urls.extend(href)
![]() | reset은 SGMLParser의 __init__ 메쏘드에 의해서 호출된다. 해석기의 실체가 생성되었다면 수동으로 호출될 수도 있다. 그래서 어떤 초기화가 필요하다면, __init__에서가 아니라, reset에서 초기화 하라. 그렇게 하면 누군가 해석기 실체를 재-사용할 때 적절하게 재-초기화가 될 것이다. |
![]() | start_a는 <a> 태그를 발견할 때마다 SGMLParser에 의해서 호출된다. 그 태그는 href 속성을 포함할 수도 있고, 그리고/혹은 name 또는 title과 같은 다른 속성들을 가질 수도 있다. attrs 매개변수는 [(attribute, value), (attribute, value), ...]로 구성된 터플의 리스트이다. 또는 그냥 <a>일 수도 있다. (필요없을 지라도) 유효한 HTML 태그로서, 이런 경우 attrs은 빈 리스트가 될 것이다. |
![]() | 이 <a> 태그가 href 속성을 가지고 있는지를 간단한 여러-변수 리스트 통합을 가지고 알아낼 수 있다. |
![]() | k=='href'과 같은 문자열 비교는 항상 대소문자에 민감하다. 그러나 이 경우에는 안전한데, 왜냐하면 SGMLParser는 attrs를 구축하는 동안에 속성 이름들을 소문자로 변환하기 때문이다. |
Example 4.7. Using urllister.py
>>> import urllib, urllister >>> usock = urllib.urlopen("http://diveintopython.org/") >>> parser = urllister.URLLister() >>> parser.feed(usock.read())>>> usock.close()
>>> parser.close()
>>> for url in parser.urls: print url
toc.html #download toc.html history.html download/dip_pdf.zip download/dip_pdf.tgz download/dip_pdf.hqx download/diveintopython.pdf download/diveintopython.zip download/diveintopython.tgz download/diveintopython.hqx [...snip...]
![]() | SGMLParser에 정의된, 먹이기(feed) 메쏘드를 호출하여, HTML을 해석기 안에 넣어라.[7] 그것은 usock.read()이 반환한 문자열을 취한다. |
![]() | 파일과 마찬가지로 처리가 끝나고 나면 최대한 빨리 사용한 URL 객체를 닫아야(close) 한다. |
![]() | 해석기 객체도 역시 닫아야(close) 한다. 그러나 다른 이유가 있다. feed 메쏘드는 먹인 HTML을 모두 처리한다고 보장할 수 없기 때문이다; 이 메쏘드는 HTML을 버퍼에 저장하고, 더 오기를 기다린다. 더 이상 없으면 close를 호출하여 버퍼를 강제 저장하여 모든 것을 완전히 해석되도록 만든다. |
![]() | 해석기가 닫혀지면(close), 해석은 끝나고 parser.urls가 HTML 문서에 있는 링크된 모든 URL의 리스트를 가진다. |
SGMLParser는 그 자체로는 아무것도 산출하지 않는다. 그저 해석하고 해석하고, 또 해석하면서 자신이 발견하는 각각의 흥미로운 것들에 적합한 메쏘드를 호출할 뿐이며, 그 자체로는 아무 일도 하지 않는다. SGMLParser는 HTML 소비자(consumer)이다: HTML을 취해 그것을 더 작은 구조화된 조각들로 분해한다. 이전 섹션에서 보았듯이, SGMLParser를 하부클래스화하면 특정한 태그들을 잡아내는 클래스를 정의할 수 있고 웹페이지에서 모든 링크들을 담은 리스트처럼, 유용한 것들을 생산해내는 클래스를 정의할 수 있다. 이제 한 걸음 더 진전시켜서 SGMLParser 해석기가 발생시키는 모든것을 나포하고 완전한 HTML문서를 재구성하는 클래스를 정의해 보자. 기술적 용어로 이런 클래스는 HTML 생산자(producer)가 될 것이다.
BaseHTMLProcessor는 SGMLParser를 하부클래스화한다 그리고 8개의 모든 핵심적인 처리 메쏘드를 제공한다: unknown_starttag, unknown_endtag, handle_charref, handle_entityref, handle_comment, handle_pi, handle_decl, and handle_data.
Example 4.8. Introducing BaseHTMLProcessor
class BaseHTMLProcessor(SGMLParser): def reset(self):self.pieces = [] SGMLParser.reset(self) def unknown_starttag(self, tag, attrs):
strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs]) self.pieces.append("<%(tag)s%(strattrs)s>" % locals()) def unknown_endtag(self, tag):
self.pieces.append("</%(tag)s>" % locals()) def handle_charref(self, ref):
self.pieces.append("&#%(ref)s;" % locals()) def handle_entityref(self, ref):
self.pieces.append("&%(ref)s;" % locals()) def handle_data(self, text):
self.pieces.append(text) def handle_comment(self, text):
self.pieces.append("<!--%(text)s-->" % locals()) def handle_pi(self, text):
self.pieces.append("<?%(text)s>" % locals()) def handle_decl(self, text): self.pieces.append("<!%(text)s>" % locals())
![]() | reset은 SGMLParser.__init__에 의하여 호출되면, self.pieces를 그 조상 메쏘드를 호출하기전에 빈 리스트로 초기화한다. self.pieces는 데이타 속성으로서 우리가 구성중인 HTML 문서의 조각들을 보유하게 될 것이다. 각 처리 메쏘드는 SGMLParser가 해석한 HTML을 재구성할 것이다. 그리고 각 메쏘드는 그 문자열을 self.pieces에 추가할 것이다. self.pieces는 리스트라는 것을 주목하라. 어쩌면 유혹에 빠져서 그것을 문자열로 정의하고 그냥 계속해서 그것에 각 조각들을 추가할 지도 모르겠다. 그것도 작동할 것이다. 그러나 파이썬에서는 리스트로 다루는게 훨씬 더 효율적이다.[8] |
![]() | BaseHTMLProcessor는 (URLLister에 있는 start_a 메쏘드와 같이) 특정한 태그들을 위한 어떠한 메쏘드로 정의하지 않으므로, SGMLParser는 모든 시작 태그(start tag)에 대하여 unknown_starttag를 호출할 것이다. 이 메쏘드는 그 태그 (tag)와 이름/값 쌍의 속성을 가진 리스트 (attrs)를 취하여 원래의 HTML을 재구성하며, 그리고 그것을 self.pieces에 추가한다. 여기에서 문자열 형식화가 약간 이상하다; 다음 섹션에서 이 문제를 해결해 보겠다. |
![]() | 종료 태그(end tags)를 재구성하는 것은 더욱 쉽다; 그냥 태그 이름을 취해서 '</...>' 괄호에 싸 넣기만 하면 된다. |
![]() | SGMLParser가 문자 참조를 발견하면, 그 참조문자를 가지고 handle_charref를 호출한다. HTML 문서에 참조  를 포함하고 있다면, ref는 160이 될 것이다. 원래의 완전한 문자 참조를 재구성하는 것은 단지 ref를 &#...;문자로 싸 넣는다는 것을 뜻한다. |
![]() | 개체 참조는 문자 참조와 비슷하다, 그러나 해쉬 표식(-)이 없다. 원래의 개체 참조를 재구성하려면 ref를 &...; 문자들 안에 싸 넣으면 된다. |
![]() | 텍스트 블록은 단순히 self.pieces에 변경되지 않은 채로 추가된다. |
![]() | HTML 주석은 <!--...-> 문자들 안에 싸여 있다. |
![]() | 명령어 처리는 <?...> 문자들 안에 싸여 있다. |
![]() | |
HTML 명세서에 의하면 (클라이언트-측 자바스크립트와 같은) 모든 비-HTML은 HTML 주석으로 둘러싸여져야 한다. 그러나 모든 웹 페이지가 이렇게 적절하게 처리하는 것은 아니다 (현대의 모든 웹 브라우저는 그렇게 하지 않더라도 용인한다). BaseHTMLProcessor는 용서가 없다; 만약 스크립트가 부적절하게 구현되었다면, 해석기는 마치 그 부적절한 스크립트가 HTML인 것처럼 해석할 것이다. 예를 들어, 만약 그 스크립트가 작거나 같다라는 사인을 포함한다면, SGMLParser는 태그와 속성을 발견했다고 잘못 생각할 것이다. SGMLParser는 항상 태그와 속성 이름을 소문자로 돌려준다. 그렇게 되면 스크립트가 망가질지도 모른다. (원래의 HTML 문서가 홑 따옴표를 사용하거나 또는 사용하지 않았을지라도) BaseHTMLProcessor는 언제나 속성 값을 겹따옴표 쌀 것이다. 그렇게 되면 확실히 스크립트가 망가질 것이다. 언제나 클라이언트-쪽 스크립트를 HTML 주석안에 보호하라. |
Example 4.9. BaseHTMLProcessor output
def output(self):"""Return processed HTML as a single string""" return "".join(self.pieces)
파이썬에는 두 개의 내장 함수가 있는데, locals와 globals이 그것으로서, 사전에-기초한 방식으로 지역변수와 전역변수에 접근을 제공한다.
먼저, 이름공간에 대하여 한마디 하자. 이것은 무미건조한 주제이지만, 중요하다. 그러므로 주목하라. 파이썬은 이름공간이라고 부르는 것을 사용하여 변수를 추적 유지한다. 이름공간은 단순히 사전이며 키는 변수의 이름이고 사전의 값은 변수의 값이다. 사실, 이름 공간은 파이썬 사전처럼 접근할 수 있다. 잠시 후에 살펴 보겠다.
파이썬 프로그램은 특정한 점으로 이름공간을 여럿 사용할 수 있다. 각 함수는 자신만의 이름공간을 가진다. 이를 지역 이름공간이라고 부르며, 함수의 변수들을 추적 유지한다. 여기에는 함수 인자와 지역적으로 정의된 변수들이 포함된다. 각 모듈은 자신만의 이름공간을 가진다. 이를 전역 이름공간이라고 부르며, 모듈의 변수들을 추적 유지한다. 함수, 클래스, 다른 모든 반입된 모듈, 그리고 모듈-수준 변수와 상수들을 포함한다. 그리고 내장 이름공간이 있는데, 모든 모듈로부터 접근가능하며, 내장 함수와 예외를 보유한다.
한 줄의 코드가 변수 x의 값을 요구하면, 파이썬은 그 변수를 모든 가능한 이름공간에서, 순서대로 찾는다:
만약 파이썬이 이러한 이름 공간 어디에서도 x를 찾지 못한다면, 파이썬은 포기하고 'There is no variable named 'x''(x 라는 이름은 없음)이라는 메시지를 가지고 NameError예외를 일으킨다. 그것을 되돌아가 제 1 장에서 본적이 있겠지만, 그 때는 파이썬이 그 에러를 주기 전에 얼마나 많은 일을 하고 있는지에 대하여 고마워 하지 않았을 것이다.
![]() | |
파이썬 2.2 는 이름공간 탐색 순서에 영향을 미치는, 미묘하지만 중요한 변화를 도입할 것이다: 내포된 영역 (nested scopes)이 바로 그것이다. 파이썬 2.0에서는 하나의 변수를 내포된 함수 또는 람다 (lambda) 함수안에서 참조할 때, 파이썬은 그 변수를 현재의 (내포된 또는 lambda) 함수의 이름 공간에서 찾고, 다음 그 모듈의 이름공간에서 탐색할 것이다. 파이썬 2.2 는 그 변수를 현재의 (내포된 또는 lambda) 함수의 이름공간에서, 다음, 부모함수의 이름공간에서, 그리고 나서 그 모듈의 이름공간에서 탐색할 것이다. 파이썬 2.1은 두 가지 방식 모두 할 수 있다; 기본적으로는 파이썬 2.0처럼 작동한다. 그러나 다음의 코드 줄을 모듈 상단부에 추가하면 모듈을 파이썬 2.2 처럼 작동시킬 수 있다:
from __future__ import nested_scopes |
파이썬에서는 다른 것들과 마찬가지로, 이름공간은 실행-시에 직접적으로 접근가능하다. 특히 지역 이름공간은 내장 locals 함수로 접근가능하고, 그리고 전역 (모듈 수준) 이름공간은 내장 globals 함수를 통하여 접근할 수 있다.
Example 4.10. Introducing locals
>>> def foo(arg):... x = 1 ... print locals() ... >>> foo(7)
{'arg': 7, 'x': 1} >>> foo('bar')
{'arg': 'bar', 'x': 1}
locals이 지역(함수) 이름 공간에 맞는다면, globals는 전역(모듈) 이름공간에 맞는다. 그렇지만, globals가 더욱 흥미로운데, 왜냐하면 모듈의 이름공간이 더욱 흥미있기 때문이다.[9] 모듈의 이름 공간은 모듈-수준의 변수와 상수들을 담고 있을 뿐만 아니라, 그 모듈에서 정의된 모든 함수와 클래스를 포함한다. 게다가, 모듈로 반입된 어떤 것이라도 포함한다.
from module import 그리고 import module사이의 차이를 기억하는가? import module을 사용하여 모듈 자체가 반입된다. 그러나 그것은 그 자신만의 이름공간을 유지하는데, 그것이 바로 모듈 이름을 사용하여 그 모듈의 함수 혹은 속성에 접근해야만 하는 이유이다: module.function와 같이 말이다. 그러나 from module import를 사용하면 실제로 다른 모듈로부터 특정한 함수와 속성들을 여러분 자신의 이름공간으로 반입하는데, 그것이 바로 그들이 유래한 원래의 모듈을 참조하지 않고서도 그들에 직접적으로 접근(할수 있는) 이유이다. globals 함수에서 실제로 이러한 현상을 볼 수있다.
Example 4.11. Introducing globals
Add the following block to BaseHTMLProcessor.py:
if __name__ == "__main__": for k, v in globals().items():print k, "=", v
![]() | 그렇게 의기소침해 하지 마라. 이전에 이 모든 것을 본 것을 기억하라. globals 함수는 사전을 반환한다. items 메쏘드와 여러-변수 할당을 사용하여 그 사전을 반복한다. 여기에서 새로운 유일한 것은 globals 함수뿐이다. |
이제 이 스크립트를 명령어 줄로부터 실행하면 이런 출력을 보여준다:
c:\docbook\dip\py>python BaseHTMLProcessor.py
SGMLParser = sgmllib.SGMLParser__doc__ = None
BaseHTMLProcessor = __main__.BaseHTMLProcessor
__name__ = __main__
__builtins__ = <module '__builtin__' (built-in)>
![]() | SGMLParser는 from module import를 사용하여 sgmllib로부터 반입된다. 그것이 뜻하는 바는 직접적으로 모듈 이름공간으로 반입되었다는 것을 뜻한다. 바로 여기에서 그렇게 반입되었다. |
![]() | 모든 모듈은 문서화 문자열(doc string)을 가지는데, 내장 속성 __doc__으로 접근할 수 있다. 이 모듈은 명시적으로 문서화 문자열이 정의되지 않았으므로, None이 기본값이 된다. |
![]() | 이 모듈은 오직 한 클래스 BaseHTMLProcessor만 정의한다. 여기에 있는 값은 그 클래스의 특별한 실체가 아니라, 그 클래스 자체임을 주목하라. |
![]() | if __name__ 꼼수를 기억하는가? 모듈이 실행될 때 (다른 모듈로 부터 반입하는 것과 대조하여), 내장 __name__ 속성은 특별한 값 __main__이다. 이 모듈을 명령어 줄로부터 스크립트로 실행하였으므로, __name__은 __main__이며, 그것이 바로 globals를 출력하기 위한 우리의 작은 테스트 코드가 실행되는 이유이다. |
![]() | |
locals와 globals 함수를 사용하여, 임의적인 변수의 값을 동적으로 얻어서, 그 변수이름을 문자열로 제공할 수 있다. 이것은 getattr 함수의 기능을 흉내내는데, 함수의 이름을 문자열로 제공함으로써 아무 함수에나 동적으로 접근할 수 있도록 하여 준다. |
내부적으로 파이썬은 실제로 (사전과 같은) 이런 방식으로 변수들을 추적한다; locals와 globals는 그러한 내부의 구현을 들여다보는 직접적인 창문이다. 그것이 바로 모든 변수, 모든 속성, 모든 데이타형에, 아무런 제한없이 작동하는 이유이다. 모든 것은 객체이다라는 나의 교훈을 이해했을 것이다. 자, 다음은 여러분에게 드리는 새로운 가르침이다: 모든 것은 사전이다.
문자열 형식화는 값들을 문자열에 삽입하기 위한 쉬운 방법을 제공한다. 값들은 터플에 나열되고 차례로 그 문자열로 각각의 형식화 표식을 대신하여 삽입되어진다. 이것은 효율적인 반면에, 항상 읽기에 가장 쉬운 코드인 것은 아니다. 특히 여러 값들을 삽입할 때는 그렇다. 단순하게 그 문자열을 한 번 훓어보고 그 결과가 어떨지 이해할 수는 없다; 계속해서 그 문자열을 읽는 것과 값들을 가진 터플을 읽는 것을 반복해 보아야한다.
값들을 가진 터플을 사용하는 대신에 사전을 사용하는 대안적인 형태의 문자열 형식화가 있다.
Example 4.12. Introducing dictionary-based string formatting
>>> params = {"server":"mpilgrim", "database":"master", "uid":"sa", "pwd":"secret"} >>> "%(pwd)s" % params'secret' >>> "%(pwd)s is not a good password for %(uid)s" % params
'secret is not a good password for sa' >>> "%(database)s of mind, %(database)s of body" % params
'master of mind, master of body'
그래서 왜 사전에-기초한 문자열 형식화를 사용하려고 하는가? 음, 다음 줄에서 키와 값들을 가지는 사전을 설정하여 단순하게 문자열 형식화를 행하는 것은 너무 과도하게 보이는 것 같다; 의미있는 키와 값들을 이미 가지고 있는 경우라면 locals처럼 실제로 대단히 유용하다.
Example 4.13. Dictionary-based string formatting in BaseHTMLProcessor.py
def handle_comment(self, text): self.pieces.append("<!--%(text)s-->" % locals())
def unknown_starttag(self, tag, attrs): strattrs = "".join([' %s="%s"' % (key, value) for key, value in attrs])self.pieces.append("<%(tag)s%(strattrs)s>" % locals())
![]()
![]() | 이 메쏘드가 호출되면, attrs은 키/값 터플의 리스트이므로, 마치 사전의 items과 같이, 여러-변수 할당을 사용하여 반복할 수 있다는 것을 의미한다. 지금까지 익숙한 패턴이리라 믿는다. 그러나 여기에서는 더 보아야 할 것이 많다. 그러므로 그것을 분해해 보자:
|
![]() | 이제 사전에-기초한 문자열 형식화를 사용하여, tag와 strattrs의 값을 문자열로 삽입한다. 그래서 tag가 'a'라면, 그 최종 결과는 '<a href="index.html" title="Go to home page">'이 될 것이다. 그리고 그것은 self.pieces에 추가된다. |
![]() | |
locals을 가지고 사전에-기초한 문자열 형식화를 사용하면 복잡한 문자열 형식화 표현식을 더 손 쉽게 읽을 수 있다. 그러나 그에는 대가가 따른다. locals에 대해 호출하면 약간의 수행성능에 충돌이 따른다. 일반적으로 걱정하는 것 만으로는 충분하지 않다. (리스트 통합을 포함하여) 문자열 형식화 표현식을 회돌이에 가지고 있다면, 아마도 정상적인 터플에-기초한 형태를 고수하는 것이 좋다. |
comp.lang.python에는 이런 질문이 흔하다. “인용부호화 되지 않은 속성 값들을 가지는 일단의 HTML 문서가 있는데, 그것들 모두를 적절히 인용부호화 하고 싶다. 어떻게 이렇게 할 수 있는가?”[10] (이런 질문은 일반적으로 프로젝트 관리자가 던지는데, 그들은 HTML은-표준-이라는 신념을 가지고 거대한 프로젝트를 결합하고 모든 페이지들은 HTML 인증자로 반드시 유효성을 검증받아야 한다고 주장한다. 인용부호화되지 않은 속성 값들은 HTML 표준에 대한 흔한 위반행위이다.) 이유야 어쨋든, 인용부호화 되지 않은 속성 값들은 HTML을 BaseHTMLProcessor에 넣어 돌리면 간단하게 고칠 수 있다.
BaseHTMLProcessor는 HTML을 먹고서 (왜냐하면 SGMLParser의 자손이기 때문이다) 동등한 HTML을 뱉아 낸다. 그러나 그 HTML 출력결과는 그 입력과 동일하지 않다. 태그와 속성 이름들은 비록 그것들이 대문자 혹은 혼합되어 시작되었을 지라도, 소문자로 결과가 날 것이다. 그리고 속성 값들은 홑따옴표나 또는 아무런 인용 부호로 시작하지 않았을 지라도, 겹따옴표 둘러 싸여지게 될 것이다. 우리가 이용할 수 있는 것은 바로 이 마지막 부작용이다.
Example 4.14. Quoting attribute values
>>> htmlSource = """... <html> ... <head> ... <title>Test page</title> ... </head> ... <body> ... <ul> ... <li><a href=index.html>Home</a></li> ... <li><a href=toc.html>Table of contents</a></li> ... <li><a href=history.html>Revision history</a></li> ... </body> ... </html> ... """ >>> from BaseHTMLProcessor import BaseHTMLProcessor >>> parser = BaseHTMLProcessor() >>> parser.feed(htmlSource)
>>> print parser.output()
<html> <head> <title>Test page</title> </head> <body> <ul> <li><a href="index.html">Home</a></li> <li><a href="toc.html">Table of contents</a></li> <li><a href="history.html">Revision history</a></li> </body> </html>
![]() | 주목할 것은 <a> 태그에 있는 href 속성의 속성 값이 적절히 인용부호화 되지 않았다는 것이다. (또한 주목할 것은 문서화 문자열(doc string) 말고 다른 어떤 것을 위하여 삼중 인용부호를 IDE 안에서 직접적으로 사용하고 있다는 것이다. (대단히 유용하다.) |
![]() | 해석기(parser)에게 먹여라. |
![]() | BaseHTMLProcessor에 정의된 output 함수를 사용하여 그 출력결과를 인용부호화 된 속성 값으로 완성된 한개의 문자열로 획득한다. 이것으로 일이 다 끝난 듯이 보이지만, 얼마나 많은 일들이 여기에서 실제로 일어나는지 생각해 보라: SGMLParser는 전체 HTML 문서를 해석하여, 그것을 tags, refs, data, 그리고 등등으로 분해한다; BaseHTMLProcessor는 그러한 요소들을 사용하여 HTML 조각들을 재구성하였다 (그것들을 보고 싶다면, parser.pieces에 여전히 저장된다); 마침내, parser.output을 호출하고 그것은 모든 HTML 조각들을 한개의 문자열로 결합한다. |
사투리 번역기(Dialectizer)는 BaseHTMLProcessor의 간단한 (그저그런) 자손이다. 텍스트 블록을 일련의 대체과정속에 실행시키지만, 확실하게 <pre>...</pre> 블록속에 있는 어떤 것도 그대로 통과시킨다.
<pre> 블록을 처리하기 위해서, 두 개의 메쏘드를 Dialectizer 안에 정의한다: start_pre 그리고 end_pre가 그것이다.
Example 4.15. Handling specific tags
def start_pre(self, attrs):self.verbatim += 1
self.unknown_starttag("pre", attrs)
def end_pre(self):
self.unknown_endtag("pre")
self.verbatim -= 1
![]() | start_pre는 SGMLParser가 <pre> 태그를 HTML 소스에서 발견할 때마다 매번 호출된다 (잠시 후에, 이것이 정확하게 어떻게 일어나는지 살펴보겠다.). 메쏘드는 한개의 매개변수 attrs를 취한다. 이 매개변수는 (만약 있다면) 그 태그의 속성을 담고 있다. attrs는 unknown_starttag가 취하는 것처럼 키/값 터플의 리스트이다. |
![]() | reset 메쏘드에서 <pre> 태그를 위한 카운터로 기능하는 데이타 속성을 하나 초기화한다. <pre> 태그를 만나게 될 때마다, 카운터가 증가한다; </pre> 태그를 만날 때마다, 카운터가 감소한다. (이것을 플래그로 단순하게 사용할 수도 있다. 그래서 1로 설정하고 0으로 재설정한다. 그러나 계수는 이것과 마찬가지로 쉽게 그렇게 할 수 있다. 그리고 계수는 내포된 <pre> 태그라는 괴이한 (그렇지만 가능한) 경우도 처리한다.) 잠시 후에, 이 카운터가 어떻게 유용하게 사용될 수 있는지를 살펴 보겠다. |
![]() | 바로 이것이다. 그것이 우리가 <pre> 태그를 위하여 한 오직 유일한 특별한 처리이다. 이제 속성들을 담은 리스트를 unknown_starttag로 연달아 건네 준다. 그래서 기본 설정된 처리를 할 수 있다. |
![]() | end_pre는 SGMLParser가 </pre> 태그를 발견할 때 마다 호출된다. 끝 태그는 속성을 포함할 수 없으므로, 메쏘드는 매개변수를 취하지 않는다. |
![]() | 첫 번째로, 다른 어떠한 끝 태그(end tag)와 마찬가지로 똑 같이 기본 설정된 처리를 하기 원한다. |
![]() | 두 번째로, 카운터를 하나 감소시켜 이 <pre> 블록은 닫혀 진다. |
이 시점에서 SGMLParser로 조금 더 깊이 파고들어가는 것이 가치가 있다. 거듭 강조하는데 (그리고 지금까지 여러분은 그것을 믿어 왔을 터인데) SGMLParser는 각각의 태그를 위한 특별한 메쏘드를, 만약 존재한다면, 찾아서 호출한다는 것이다. 예를 들어, 방금 start_pre와 end_pre의 정의가 <pre>과 </pre>를 처리하는 것을 보았다. 그러나 어떻게 이것이 가능한가? 음, 그것은 마법이 아니다. 그것은 단지 파이썬의 훌륭한 코딩법일 뿐이다.
def finish_starttag(self, tag, attrs):try: method = getattr(self, 'start_' + tag)
except AttributeError:
try: method = getattr(self, 'do_' + tag)
except AttributeError: self.unknown_starttag(tag, attrs)
return -1 else: self.handle_starttag(tag, method, attrs)
return 0 else: self.stack.append(tag) self.handle_starttag(tag, method, attrs) return 1 def handle_starttag(self, tag, method, attrs): method(attrs)
![]() | 이 시점에서 SGMLParser는 이미 시작 태그(start tag)를 발견하고 속성 리스트를 해석하였다. 해야 할 남은 유일한 일은 이 태그를 위한 특정한 처리 메쏘드가 있는지 혹은 기본 메쏘드(unknown_starttag)에 의존해야 하는 지를 알아내는 일이다 . |
![]() | SGMLParser의 “마법”은 우리의 오랜 친구 getattr에 불과하다. 이전에 깨닫지 못했을 수도 있는 것은 getattr이 한 객체의 자손들과 그 객체 자신에 정의된 메쏘드를 찾을 것이다라는 것이다. 여기에서 그 객체는 self 즉 현재의 실체이다. 그래서 tag가 'pre'라면, 이러한 getattr호출은 Dialectizer 클래스의 실체인 현재 실체에서 start_pre 메쏘드를 찾을 것이다. |
![]() | 자신이 찾고 있는 메쏘드가 객체에 존재하지 않는다면 (혹은 그의 자손 어디에도 없다면), getattr은 AttributeError를 일으킨다. 그러나 그래도 문제가 안된다. 왜냐하면 getattr에 대한 호출을 try...except 블록 안에 싸넣고 그리고 명시적으로 AttributeError를 나포했기 때문이다. |
![]() | start_xxx 메쏘드를 발견하지 못했으므로, 포기하기 전에 do_xxx 메쏘드도 찾아본다. 이러한 이름 대체 전략은 <br>과 같은 상응하는 끝 태그를 가지지 않는 독립적 태그를 위해 일반적으로 사용된다. 그러나 이름짓기 전략도 사용할 수 있다; 보시다시피, SGMLParser는 모든 태그에 대하여 둘 다를 시도한다. (그렇지만, 여러분은 start_xxx와 do_xxx 처리 메쏘드를 같은 태그에 둘 다 정의해서는 안 된다; 오직 start_xxx 메쏘드만이 호출될 것이다.) |
![]() | 또 하나의 AttributeError, 그것은 getattr에 대한 호출이 do_xxx에 실패하였다는 것을 의미한다. start_xxx도 do_xxx 메쏘드도 발견하지 못하였으므로, 그 예외를 나포한다. 그리고 기본 메쏘드 unknown_starttag에 의지한다. |
![]() | 기억할 것은 try...except 블록은 else 절을 가질 수 있다는 것이다. else 절은 try...except 블록을 실행하는 동안에 어떤 예외도 일어나지 않으면 호출된다. 논리적으로, 그것은 이 태그를 위한 do_xxx 메쏘드를 발견하였다는 것을 의미한다. 그래서 이것을 호출할 것이다. |
![]() | start_xxx와 do_xxx 메쏘드는 직접적으로 호출되지 않는다; 태그, 메쏘드, 그리고 속성들은 handle_starttag 함수에 건네진다. 그래서 자손들은 그것을 덮어쓸 수 있으며 그리고 모든 시작 태그가 보내어지는 방식을 변경할 수 있다. 그 정도 수준의 제어는 필요 없으므로, 단지 이 메쏘드가 일을 하도록 그대로 두기만 하면 된다. 그 일이란 그 메쏘드를 (start_xxx 또는 do_xxx) 속성들의 리스트로 호출하는 것이다. 기억할 것은 method는 함수이며, getattr으로 부터 반환되고, 함수는 객체라는 것이다. (귀가 따갑도록 이 말을 들어왔을 것이다. 그것을 이용하는 새로운 방식을 찾기를 그만 두자 마자 나는 그 말을 하는 것을 중지하겠다고 약속한다.) 여기에서 그 함수 객체는 보내기 메쏘드에 인자로 건네어진다. 그리고 이 메쏘드는 되돌아서 그 함수를 호출한다. 이 시점에서 그 함수가 무엇인지, 그 이름이 무엇인지, 혹은 어디에서 정의 되었는지 알 필요는 없다; 그 함수에 대해서 알아야 할 유일한 것이 있다면 그것이 한 개의 인자 attrs로 호출된다는 것이다. |
이제 계획된 프로그램으로 되돌아가자: Dialectizer로 말이다. 떠날 때 <pre>과 </pre> 태그를 위한 특별한 처리 메쏘드를 정의하고 있었다. 오직 한가지 일만이 남아 있다. 그것은 텍스트 블록을 미리-정의한 대치물로 처리하는 것이다. 그렇게 하려면 handle_data 메쏘드를 덮어 쓸 필요가 있다.
Example 4.17. Overriding the handle_data method
def handle_data(self, text):self.pieces.append(self.verbatim and text or self.process(text))
![]() | handle_data는 오직 하나의 인자, 처리되야할 텍스트로 호출된다. |
![]() | 그 조상 BaseHTMLProcessor에서, handle_data 메쏘드는 단순하게 그 텍스트를 그 출력 버퍼 self.pieces에 추가한다. 여기에서 그 논리는 약간 더 복잡한 정도이다. <pre>...</pre> 블록안에 있다면, self.verbatim은 0보다는 큰 어떤 값이 될 것이다. 그리고 텍스트를 출력 버퍼에 변경하지 않은 채로 넣기를 원한다. 그렇지 않으면, 따로 메쏘드를 호출하여 그 대치를 처리하고, 처리 결과를 출력 버퍼로 넣게 될 것이다. 파이썬에서 이것은 and-or 꼼수를 사용한 한-줄 짜리 코드이다. |
거의 Dialectizer를 이해하게 되었다. 한가지 빠진 점이라면 그 텍스트 대치 자체의 성질이다. 조금이라도 펄을 안다면, 복합 텍스트 대치가 필요할 때 유일한 실제적 해법은 정규 표현식이라는 것을 알 것이다.
정규 표현식은 복잡한 패턴의 문자열을 가진 텍스트를 해석하고, 대치하며, 탐색하는 강력한 (그리고 대단히 표준화된) 방법이다. (펄과 같은) 다른 언어에서 정규 표현식을 사용해 보았다면, 이 섹션을 건너 뛰고 단순히 re 모듈의 요약을 읽고서 가능한 함수들과 그들의 인자들을 한 번 살펴보라.
문자열은 탐색 (index, find, 그리고 count), 대치 (replace), 그리고 해석(split)을 위한 메쏘드를 가진다. 그러나 가장 단순한 경우에 한정되어 있다. 탐색 메쏘드는 한개의 긴밀히-작성된 하부문자열을 찾는다. 그리고 항상 대-소문자에 민감하다; 문자열 s를 대소문자 민감 탐색을 하려면, s.lower() 또는 s.upper()를 호출해야 하고 그리고 탐색 문자열이 대소문자에 맞는지 확인해야 한다. replace와 split 메쏘드도 같은 제한을 가진다. 할수만 있다면 그것들을 사용해하는 것이 좋다 (빠르고 읽기에 쉽기 때문이다). 그러나 좀 더 복잡한 일을 하려면 정규 표현식으로 단계를 높여야만 할 것이다.
Example 4.18. Matching at the end of a string
새로운 시스템으로 반입하기 전에 기존의 시스템으로부터 반출되어온 거리 주소를 표준화하고 제거하는, 이러한 일련의 예제들은 본인의 실제-삶의 문제에서 고무된 것이다 (그저 재미로 만들지 않았으며; 실제로 쓸모가 있다.)
>>> s = '100 NORTH MAIN ROAD' >>> s.replace('ROAD', 'RD.')'100 NORTH MAIN RD.' >>> s = '100 NORTH BROAD ROAD' >>> s.replace('ROAD', 'RD.')
'100 NORTH BRD. RD.' >>> s[:-4] + s[-4:].replace('ROAD', 'RD.')
'100 NORTH BROAD RD.' >>> import re
>>> re.sub('ROAD$', 'RD.', s)
![]()
'100 NORTH BROAD RD.'
Example 4.19. Matching whole words
>>> s = '100 BROAD' >>> re.sub('ROAD$', 'RD.', s)'100 BRD.' >>> re.sub('\\bROAD$', 'RD.', s)
'100 BROAD' >>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD' >>> s = '100 BROAD ROAD APT. 3' >>> re.sub(r'\bROAD$', 'RD.', s)
'100 BROAD ROAD APT. 3' >>> re.sub(r'\bROAD\b', 'RD.', s)
'100 BROAD RD. APT 3'
이것은 정규 표현식이 할 수 있는 일 중 빙산의 일각일 뿐이다. 엄청나게 강력하며 모두 다루려면 한 권의 책이 들 것이다. 물론 모든 문제에 대하여 옳은 해법은 아니다. 정규 표현식에 대해서 충분히 배워서 언제 그것들이 적절한지를 알아야 하고, 그리고 언제 그것들이 문제를 풀기보다는 문제를 일으키기만 하게 될 지를 알아야만 한다.
어떤 사람들은 문제에 봉착하면, “알겠어, 정규 표현식을 써야겠군”이라고 생각한다. 이제 그들은 두 개의 문제에 봉착한다. | ||
--Jamie Zawinski, in comp.lang.emacs |
더 읽어야 할 것
미안하지만, 이 장의 마지막에 도달 했으며 이것이 지금까지 작성된 모든 것이다. http://diveintopython.org/에로 돌아가 혹시 갱신된 것이 있는지 점검하여 보라.
[7] SGMLParser와 같은 해석기를 위한 기술적인 용어는 소비자 (consumer)이다: 그것은 HTML을 먹고서 분해한다. 미리 예상했겠지만, 그 이름 먹이감(feed)는 “소비자 (consumer)”라는 전체 분위기에 맞도록 선택되었다. 개인적으로 그것은 아무런 나무, 식물, 혹은 다른 어떤 생명의 흔적도 없는 캄캄한 우리만 있는 동물원에서의 전시회를 생각나게 한다. 그러나 꿋꿋하게 똑 바로 서서 실제로 가까이 살펴 보면 두 개의 부리부리한 눈이 저 뒤쪽 모퉁이로부터 쏘아보고 있다는 것을 느낄 수 있다. 그러나 여러분은 스스로 위로하기를 저것은 단지 마음이 속임수를 쓰고 있는 것이다라고 위로한다. 그리고 "그 전체 사건은 단순히 비어있는 우리가 아니다"라고 확실하게 말할 수 있는 유일한 이유는 철장위에 작은, 그나마 무섭지 않은 표지에 이렇게 씌여 있기 때문이다. “해석기 (parser)에게 먹이를 주지 마시오.” 그러나 아마도 그것이 나 일지도 모른다. 어쨋든, 그것은 흥미로운 심상 (마음의 그림)이다.
[8] 파이썬이 문자열보다 리스트에 더 강한 이유는 리스트는 교환가능하지만 문자열은 교환불가능하기 때문이다. 이것은 리스트에 추가하는 것이 단지 그 요소를 추가하고 그 지표를 갱신하기만 하면 된다는 것을 뜻한다. 문자열은 생성된 후에는 변경할 수 없으므로, s = s + newpiece와 같은 코드는 원래의 문자열과 그 새로운 문자열 조각을 연결함으로써 완전하게 새로운 문자열을 생성할 것이다, 그리고는 그 원래의 문자열을 파기할 것이다. 이렇게 하면 소비적인 메모리 관리를 많이 따른다. 그리고 수반되는 노력의 양은 문자열이 길어짐에 따라서 증가한다. 그래서 s = s + newpiece를 회돌이에 사용하는 것은 치명적이다. 기술적인 용어로 이야기 해서, n개의 항목을 리스트에 추가하는 것은 O(n)이다, 반면에 n개의 항목을 문자열에 추가하는 것은 O(n2)이다.
[9] 많은 것을 언급하지 않겠다.
[10] 좋다, 그렇게 흔한 질문은 아니다. 거기에는 이런 질문들은 없다: “파이썬 코드를 작성하기 위해서는 나는 어떤 편집기를 사용해야 하는가?” (대답: Emacs) 혹은 “파이썬은 펄보다 나은가 못한가?” (대답: “펄이 파이썬보다 못하다 왜냐하면 사람들이 그것이 못하기를 원하기 때문에.” -Larry Wall, 10/14/1998) 그러나 HTML 처리에 관한 질문들은 한 달에 한 번꼴로 한 형태 이상 출현한다. 그리고 그러한 질문들 중에, 이것이 가장 인기있는 질문이다.
제 3 장 객체지향 작업틀 | 목 차 | 제 5 장 유닛 테스트 >> |