Elements and Element Trees
Fredrik Lundh | Last updated July 2007
한글판 johnsonj 2008.11.01 토
이 글은 effbot.org elementtree 라이브러리에서 사용할 수 있는 Element와 SubElement 그리고 ElementTree 유형을 소개한다.
개관을 보려면, 더 많은 문서와 글로 가는 링크로 가득한 ElementTree 개관 페이지에 가보자.
API 참조서가 필요하면 elementtree.ElementTree 모듈을 참고하자.
effbot.org 내려받기 페이지에서 내려받을 수 있다.
이 글에서 다룰 내용:
Element 유형 #
Element 유형은 유연한 컨테이너 객체로서 계통적인 데이터 구조를 메모리에 저장하기 위하여 설계되었다. 유형을 기술하자면 대략 리스트와 사전의 중간쯤 된다.
각 원소는 그에 연관된 특성들을 가진다:
- 태그(tag). 태그는 이 원소가 어떤 종류의 데이터를 대표하는지 식별하는 문자열이다 (즉 element 유형이다).
- 수 많은 속성(attributes), 파이썬 사전에 저장된다.
- 텍스트 내용을 담을 text 문자열 그리고 뒤에 오는 텍스트를 담을 tail 문자열
- 수 많은 자손 원소들(child elements), 파이썬 연속열에 저장된다
원소는 모두 반드시 태그가 있지만, 다른 특성들은 선택적이다. 모든 문자열은 유니코드 문자열이거나 오직 US-ASCII 문자만 담고 있는 8-비트 문자열이다.
원소를 만들려면 Element 구성자를 호출하고 태그 문자열을 첫 인자로 건네면 된다:
from elementtree.ElementTree import Element root = Element("root")
tag 속성을 통하여 태그 문자열에 접근할 수 있다:
print root.tag
트리를 구축하려면, 더 많은 원소들을 만들어서 그 원소들을 부모 원소에 추가하면 된다:
root = Element("root") root.append(Element("one")) root.append(Element("two")) root.append(Element("three"))
이는 아주 자주 보는 연산이므로, SubElement라는 도움 함수를 제공한다. 단 한 번에 이 함수는 새로운 원소를 만들어서 그의 부모에 추가한다:
from elementtree.ElementTree import Element, SubElement root = Element("root") SubElement(root, "one") SubElement(root, "two") SubElement(root, "three")
하위원소에 접근하려면 보통의 리스트 (연속열) 연산을 사용하면 된다. len(element)로 하위 원소의 개수를 얻고, element[i]로 i’번째 하위원소를 가져오며 그리고 for-in 서술문으로 하위원소들을 회돌이한다:
for node in root: print node
element 유형은 (조각썰기 할당을 포함하여) 조각썰기도 지원하며, 표준 append, insert 그리고 remove 메쏘드를 지원한다:
nodes = node[1:5] node.append(subnode) node.insert(0, subnode) node.remove(subnode)
remove는 원소를 받지 태그를 받지 않음에 주목하자. 삭제할 원소를 찾으려면 부모를 회돌이하거나 아래에 기술한 find 메쏘드중의 하나를 사용하면 된다.
진리 테스트 #
ElementTree 1.2 이전에서 연속열의 행위는 원소가 하위원소가 없는 원소는 (빈 연속열이므로) 거짓으로 검증되었다. 비록 텍스트나 속성이 안에 있을지라도 말이다. 함수나 메쏘드가 노드 대신에 None을 반환할지도 모르기 때문에 반환 값을 점검하기 위하여 명시적으로 테스트해야 한다.
def fetchnode(): ... node = fetchnode() if not node: # 주의하라! print "node not found, or node has no subnodes" if node is None: print "node not found"
주의: 이런 행위는 ElementTree 1.3에서 약간 바뀔 가능성이 있다. 이를 코딩하려면 두 방향에서 호환성이 있게 작성해야 하므로 "element is None"을 이용하여 원소의 존재 여부를 테스트하고 "len(element)"으로 원소가 비어 있는지 없는지 테스트하자.
부모에 접근하기 #
element 구조는 명시적으로 부모를 가리키지 않는다. 자손/부모 관계를 추적 유지할 필요가 있다면, 프로그램을 자손보다 부모에 작동하도록 구조화하자:
for parent in tree.getiterator(): for child in parent: ... 부모/자손 터플을 처리한다
getiterator 함수는 아래에 더 자세하게 설명한다.
이런 일을 많이 하면, 그 반복자 코드를 발생자 함수 안에 싸 넣자:
def iterparent(tree): for parent in tree.getiterator(): for child in parent: yield parent, child for parent, child in iterparent(tree): ... 부모/자손 터플을 처리한다
또다른 접근법은 따로 데이터 구조를 사용하여 자손 원소와 그의 부모를 짝짓는 것이다. 파이썬 2.4 이후로, 다음의 한-줄 코드는 전체 트리에 대하여 자손/부모 짝짓기표를 만든다:
parent_map = dict((c, p) for p in tree.getiterator() for c in p)
속성 #
테그 그리고 하위원소 리스트와 더불어, 각 원소는 여러 속성을 가질 수 있다. 각 원소 속성은 문자열 키 하나와 상응하는 값으로 구성된다. 평범한 파이썬 사전처럼 모든 키는 유일해야 한다.
Element 속성은 실제로 표준 파이썬 사전에 저장된다. 그러므로 attrib 속성을 통하여 접근할 수 있다. 속성을 설정하려면, 그냥 attrib 구성원에 할당하면 된다:
from elementtree.ElementTree import Element elem = Element("tag") elem.attrib["first"] = "1" elem.attrib["second"] = "2"
새로 원소를 만들 때 키워드 인자를 사용하여 원소 속성을 건넬 수 있다. 앞의 예제는 다음과 같이 하면 더 잘 작성할 수 있다:
from elementtree.ElementTree import Element elem = Element("tag", first="1", second="2")
Element 유형은 attrib.get, attrib.keys, 그리고 attrib.items 같은 편의기능도 제공한다. 원소 속성의 값을 설정하기 위하여 set 메쏘드도 있다:
from elementtree.ElementTree import Element elem = Element("tag", first="1", second="2") # 'first' 속성을 인쇄한다. print elem.attrib.get("first") # 같지만, 편의기능을 이용한다. print elem.get("first") # 키 리스트를 인쇄한다 (편의기능 이용). print elem.keys() print elem.items() # 'third' 속성은 존재하지 않는다. print elem.get("third") print elem.get("third", "default") # 속성을 추가하고 다시 시도한다. elem.set("third", "3") print elem.get("third", "default") 1 1 ['first', 'second'] [('first', '1'), ('second', '2')] None default 3
attrib 값은 실제로 변경가능한 파이썬 사전이어야 하지만, ElementTree 구현은 또다른 내부 표현을 사용할 수도 있다. 그리고 누군가 요구할 경우에만 사전을 만들기도 한다. 그런 구현을 활용하려면, 가능하면 편의기능을 이용하자.
텍스트 내용 #
element 유형은 text 속성도 제공하는데, 이 속성은 그 원소와 연관된 추가 데이터를 보유하는데 사용될 수 있다. 그 이름이 암시하듯이, 이 속성은 보통 텍스트 문자열을 보유하는데 사용되지만, 다른 어플리케이션에 종속적인 목적으로 사용될 수도 있다.
from elementtree.ElementTree import Element elem = Element("tag") elem.text = "this element also contains text"
추가 데이터가 없다면, 이 속성은 빈 문자열이나 None으로 설정된다.
element 유형은 실제로 이런 식으로 사용될 수 있는 속성을 두 가지 제공한다; text 외에도, 비슷한 속성으로 tail이 있다. 이 속성 역시 텍스트 문자열이나 어플리케이션 종속적 객체 또는 None을 담을 수 있다. tail 속성은 혼합-내용 XML 파일을 읽을 경우 뒤따르는 텍스트 노드를 저장하는데 사용된다; 한 원소 뒤에 바로 따르는 텍스트는 그 원소의 tail 속성으로 저장된다:
<tag><elem>this goes into elem's text attribute</elem>this goes into elem's tail attribute</tag>
더 자세한 정보는 혼합 내용 섹션을 참고하자.
어떤 구현은 오직 문자열 객체만 text나 tail 값으로 지원한다.
예제
# elementtree-example-1.py from elementtree.ElementTree import Element, SubElement, dump window = Element("window") title = SubElement(window, "title", font="large") title.text = "A sample text window" text = SubElement(window, "text", wrap="word") box = SubElement(window, "buttonbox") SubElement(box, "button").text = "OK" SubElement(box, "button").text = "Cancel" dump(window)
$ python elementtree-example-1.py <window><title font="large">A sample text window</title><text wrap= "word" /><buttonbox><button>OK</button><button>Cancel</button></but tonbox></window>
하위 원소 검색하기 #
Element 유형은 하위원소를 찾는데 사용될 수 있는 메쏘드를 많이 제공한다.:
find(pattern)은 주어진 패턴에 일치하는 첫 하위원소를 돌려준다. 일치하는 원소가 없으면 None을 돌려준다.
findtext(pattern)은 주어진 패턴에 일치하는 첫 하위원소의 text 속성의 값을 돌려준다. 일치하는 원소가 없으면, None을 돌려준다.
findall(pattern) 주어진 패턴에 일치하는 하위원소들을 모두 리스트에 담아 (또는 또다른 반복가능 객체를) 돌려준다.
ElementTree 1.2 이후로, 패턴 인자는 태그 이름 또는 경로 표현식이 될 수 있다. 태그 이름이 주어지면, 직계 하위 원소만 점검된다. 경로 표현식은 전체 하위트리를 검색하는데 이용할 수 있다.
ElementTree 1.1 이전에서는 오직 평범한 태그 이름만 지원한다.
게다가, getiterator 메쏘드는 트리를 깊이-우선 순위로 회돌이하는데 사용될 수 있다:
getiterator(tag)는 그 하위트리의 모든 수준에서 주어진 태그가 있는 모든 하위요소들을 리스트(또는 또다른 반복가능 객체)에 담아 돌려준다. 원소는 문서 순서로 (즉, 트리를 XML 파일로 저장하면 나타나는 순서대로) 반환된다.
getiterator() (인자 없이) 하위트리에 있는 모든 하위요소들을 리스트(또는 또다른 반복자 객체)에 담아 돌려준다.
getchildren()은 모든 직계 자손 요소들을 리스트(또는 또다른 반복자 객체)에 담아 돌려준다. 이 메쏘드는 낡았다; 새로운 조각썰기나 인텍스 코드를 사용하여 자손에 접근하거나 list(elem)를 이용하여 리스트를 얻어야 한다.
XML 파일 읽고 쓰기 #
Element 유형은 XML 파일을 메모리에 표현하는데 사용할 수 있다. ElementTree 포장 클래스는 XML 파일을 읽고 쓰는데 사용된다.
XML 파일을 Element 구조 안으로 적재하려면 parse 함수를 사용하면 된다:
from elementtree.ElementTree import parse tree = parse(filename) elem = tree.getroot()
파일 핸들을 건네도 된다 (또는 read 메쏘드만 있으면 어떤 객체도 상관없다):
from elementtree.ElementTree import parse file = open(filename, "r") tree = parse(file) elem = tree.getroot()
parse 메쏘드는 ElementTree 객체를 돌려준다. 최상위 element 객체를 얻으려면 getroot 메쏘드를 사용하자.
최신 버전의 ElementTree 모듈이라면 단번에 file 키워드 인자를 이용하여 트리를 만들어서 파일의 내용으로 채워도 된다:
from elementtree.ElementTree import ElementTree tree = ElementTree(file=filename) elem = tree.getroot()
원소 트리를 다시 디스크에 저장하려면 ElementTree 클래스의 write 메쏘드를 사용하자. parse 함수처럼, 파일이름이나 파일 객체를 받는다 (또는 어떤 객체든지 write 메쏘드만 있으면 된다):
from elementtree.ElementTree import ElementTree tree = ElementTree(file=infile) tree.write(outfile)
Element 객체 계통도를 디스크에 저장하려면 ElementTree 실체에 싸넣자:
from elementtree.ElementTree import Element, SubElement, ElementTree html = Element("html") body = SubElement(html, "body") ElementTree(html).write(outfile)
표준 element 작성기는 간결하게 출력함을 주목하자. 예쁜 출력이나 사용자-정의 이름 공간 접두사는 현재 버전에서 내장 지원하지 않는다. 그래서 언제나 사람이 보기 편하게 출력되지는 않는다 (다시 말해 XML이 사람이 읽기에 적당하지 않다).
더 깔끔하게 출력하는 방법은 저장하기 전에 공백문자를 추가하는 것이다; Element 라이브러리 함수 페이지에서 indent 함수 예제를 참조하자.
XML과 문자열 사이를 변환하려면 XML, fromstring, 그리고 tostring 도움자를 사용할 수 있다:
from elementtree.ElementTree import XML, fromstring, tostring elem = XML(text) elem = fromstring(text) # 다음과 동일 XML(text) text = tostring(elem)
XML 이름공간 #
elementtree 모듈은 원소 태그와 속성 이름에 대하여 자격있는 이름(QNames)을 지원한다. 자격있는 이름은 (uri, local name) 쌍으로 구성된다.
자격있는 이름은 XML 이름공간 규격에 소개되었다.
element 유형은 자격있는 이름 쌍을 나타내며, 범용 이름으로 불리우기도 한다. "{uri}local" 형태의 문자열로서 이 구문은 태그 이름과 속성 키에 모두 사용이 가능하다.
다음 예제는 태그가 자격있는 이름 쌍 (http://spam.effbot.org, egg)인 원소를 만든다.
from elementtree.ElementTree import Element elem = Element("{http://spam.effbot.org}egg"}
이를 XML 파일로 저장하면, 작성기는 자동으로 적절한 XML 이름공간 선언을 생성하고, 적절한 접두사를 선택한다. XML 파일을 적재하면, 해석기는 자격있는 태그와 속성 이름을 element 구문으로 변환한다.
표준 해석기는 이름공간 접두사와 선언을 버림에 주목하자. 그래서 나중에 접두사에 접근할 필요가 있다면 (예, 속성 값 또는 문자 데이터에 있는 자격있는 이름을 처리하려면), 다른 해석기를 사용해야 한다. 이 주제에 관한 더 자세한 정보는 다음 글 The ElementTree iterparse Function 그리고 Using the ElementTree Module to Generate SOAP Messages, Part 3: Dealing with Qualified Names를 참조하자.