Anytree(파이썬 트리 라이브리리)
14 Feb 2019 | Programming Python작업을 하다 보면 트리 형식으로 구현해야 할 때가 꽤 생긴다. C라면 한땀한땀 구조체로 구현해서 사용해야겠지만, 파이썬 같은 high-level의 언어는 트리 라이브러리를 지원한다. 이번 포스팅에서는 파이썬의 꽤 강력한 트리 라이브러리인 “Anytree”를 다룬다. 이 라이브러리는 트리의 탐색, 노드의 추가/삭제 등을 간편하게 구현할 수 있게 만들어 놓았다. 뒤에 보면 알겠지만 메소드들이 매우 직관적이고 쓰기 쉽게 만들어 놓았다. 게다가 트리를 시각화해서 출력하는 기능까지 제공한다.
[Installation]
일단, 공식 documentation은 여기를 들어가서 볼 수 있다.
간단히 pip install anytree 또는 conda install anytree로 설치할 수 있다.
[Library Import]
from anytree import Node, RenderTree
from anytree.exporter import DotExporter
위의 코드로 anytree를 import해온다.
[Making Nodes]
이제부터는 Node()라는 함수를 통해서 트리의 노드를 정의내릴 수 있다. documentation에 있는 코드를 기준으로 설명하면,
udo = Node("Udo")
marc = Node("Marc", parent=udo)
lian = Node("Lian", parent=marc)
dan = Node("Dan", parent=udo)
jet = Node("Jet", parent=dan)
jan = Node("Jan", parent=dan)
joe = Node("Joe", parent=dan)
위와 같이 7개의 노드로 구성되고 root가 udo인 트리를 만들었다.
Node는 위와 같은 형식으로 만든다. 먼저 쌍따옴표로 Node의 name을 정하고(이는 나중에 udo.name으로 접근할 수 있다) 그 뒤에는 parent를 어떤 노드로 할 것인지 정한다. 이렇게 설정하면 자동적으로 marc의 부모 노드는 udo, udo의 자식 노드는 marc라고 인식하게 된다. 그러니까 노드를 잇고 끊을 때는 parent만 신경써주면 된다는 뜻이다.
한 가지 주의할 점. Node들을 연결할 때 해당 노드를 가리키는 변수로 연결해야 한다. 그러니까 udo에 연결해야 하지, Node(“Udo”) 에 연결하면 안 된다는 거다. 모든 노드는 변수처럼 선언되어야 사용할 수 있다.
Node에는 parent라는 argument 말고도 사용자가 직접 argument를 추가할 수도 있다.
data = Node("Data", data_index = 4, data_spec = "Key", mat = [1,2,3])
이런 식으로 내 마음대로 Node에다가 데이터를 집어넣을 수 있다. 데이터의 형식은 아무래도 상관없다.
다만, 모든 트리의 argument들은 다 똑같아야 한다. 위처럼 Node를 또 만들 때 mat이 없는 Node라면 mat = [] 또는 mat = None 으로 설정해줘야 한다는 말이다.
[Visualization]
for pre, fill, node in RenderTree(udo):
print("%s%s" % (pre, node.name))
위와 같은 코드를 사용하면 udo를 root로 하는 트리를 시각화해서 볼 수 있다.
Udo
├── Marc
│ └── Lian
└── Dan
├── Jet
├── Jan
└── Joe
이런 식으로 아름답게 시각화되어 출력된다. 직접 실험해본 결과 엥간히 큰 규모의 트리도 txt 파일에 저장시키면 다 시각화시켜서 볼 수 있다. 사실상 이 라이브러리의 가장 강력한 기능이 아닐까 한다.
[Methods of Node]
Node를 선언하고 .을 덧붙이면 메소드들을 볼 수 있는데, 메소드들은 다음과 같다 :
- ancestors : 해당 노드의 조상 노드들을 모두 반환한다(tuple). 바로 위만 반환하는 게 아니라 그 위, 그 위…root까지 모두 반환한다.
- anchestors : ancestors와 같음. 사용하지 말 것.
- children : 해당 노드의 자식 노드들을 모두 반환한다(tuple). 자신에게 직접 연결된 자식 노드만.
- depth : 해당 노드의 깊이를 반환한다.(int) root Node는 0을 반환
- descendants : 해당 노드의 자손 노드들을 모두 반환한다(tuple). 자식의 자식의 자식…까지 전부 반환
- height : 해당 노드의 높이를 반환한다.(int) leaf Node는 0을 반환
- is_leaf : 해당 노드가 leaf인지 아닌지를 반환한다(bool).
- is_root : 해당 노드가 root인지 아닌지를 반환한다(bool).
- name : 해당 노드의 이름을 반환한다(str). Node 정의할 때 큰따옴표 안에 있던 그것.
- parent : 해당 노드의 부모 노드를 반환한다.(Node)
- path : root부터 해당 노드까지의 path를 반환한다.(tuple)
- root : 해당 노드가 속한 트리의 root Node를 반환한다.(Node)
- separator : 해당 노드의 구분자를 출력한다. 별로 쓸 일은 없다.
- siblings : 해당 노드와 같은 부모를 가진 노드들을 출력한다(tuple)
사실상 노드에서 알아내야 하는 모든 정보들을 메소드들로부터 알 수 있다. 매우 파워풀.
[Attatch / Detach]
트리에 노드를 추가 / 삭제하는 방법 또한 매우 간단하다.
- 추가할 땐 노드를 새로 정의하면서 parent argument에 parent로 삼고 싶은 노드를 써 주면 된다. 이러면 알아서 부모 노드는 자식을 알아본다.
- 삭제할 땐 해당 노드의 parent를 None으로 설정하면 끝난다. 예를 들어 data라는 Node를 트리에서 제거하고 싶으면
data.parent = None
이라는 코드 한 줄로 끝난다. 만약 이 노드가 leaf 노드가 아니라면, data 노드를 root로 하는 새로운 트리가 만들어진 것이다. 메모리 문제 때문에 트리에서 떼낸 Node를 제거하고 싶다면del data
코드를 추가해주자.
이 정도면 자유롭게 라이브러리를 통해 tree를 사용할 수 있을 것이다.
실제 사용할 때는 Node들을 모아 놓은 list를 하나 만들거나 해서 함께 관리해 주는 게 좀더 효율적이다.
Comments