this recipe describes a tree backbone class that supports two modes of iteration and topological comparison. and it has a nice node access inteface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | ## for python 2.2+
from __future__ import generators
__version__ = '1.0.12'
#--------------------------------------------------------------CTree---
class CTree:
'''Generate a Class Tree.
Tree characteristics and restrictions:
- all children of any single node must have unique names.
- the tree nodes are sorted in order of definition.
- due to the fact that in standard python the recursion depth
is limited (1000 by default) this tree can not exceed this
limit.
- all node names must comply with identifier naming rules for
the python language (i.e. [_a-zA-Z][_0-9a-zA-Z]*).
the interface is as follows:
- on init the path is a dot separated branch to be created.
- on call if the path exists return the last mentioned element
else create the part that does not exist and return the last
element.
'''
def __init__(self, path='', separator='.'):
'''
Generate a tree branch corresponding to the path given.
path : a <separator> delimited path to be created.
separator : the path delimiter ('.' by default)
'''
self.__leafs = '_' + self.__class__.__name__ + '__leafs' in self.__dict__ and self.__leafs or []
try:
c_path = (path.split(separator,1)[0], path.split(separator,1)[1])
if not c_path[0] in self.__dict__:
# generate the tail of the branch
setattr(self, c_path[0], self.__class__(c_path[1], separator))
self.__leafs.append(self.__dict__[c_path[0]])
else:
# go down one level
getattr(self, c_path[0]).__init__(c_path[1], separator)
except IndexError:
# do this if we are at the end of the path/branch
c_path = path.split(separator,1)[0]
path != '' and not c_path in self.__dict__ and \
not self.__dict__.update({c_path: self.__class__()}) and \
not self.__leafs.append(self.__dict__[c_path])
# this acts as a proxy to __init__
def __call__(self, path='', separator='.'):
'''
Generate a tree branch corresponding to the path given.
path : a <separator> delimited path to be created.
separator : the path delimiter ('.' by default)
'''
self.__init__(path, separator)
return eval('self' + (path!='' and '.' + (separator != '.' and path.replace(separator,'.') or path) or ''))
def __eq__(self, other):
return self.__class__ == other.__class__ and \
len(self.__leafs) == len(other.__leafs) and \
not ( 0 in [ l0 == l1 for l0, l1 in zip(self.__leafs, other.__leafs) ] )
def __ne__(self, other):
return not (self == other)
def __iter__(self, func=lambda lst, tail: lst + tail):
'''
this generator will iterate through the tree (left-to-right
depth-first).
func : a function taking two parameters, the first is the list
of sub-nodes of the current node, the second is the
stack representing the tail of the returning node list
(excluding the returned nodes). And returning a
combined list of nodes.
'''
node_list = [self]
while 1:
try:
cur = node_list.pop(0)
node_list = func(cur.__leafs, node_list)
yield cur
except AttributeError:
yield cur
except IndexError:
return
def flat_iter(self):
'''
left-to-right breadth-first generator.
'''
return self.__iter__(lambda lst, tail: tail + lst)
def __delattr__(self, name):
try:
self.__dict__[name] in self.__leafs and self.__leafs.remove(self.__dict__[name])
del self.__dict__[name]
except KeyError:
raise AttributeError
#----------------------------------------------------------------------
|
this code is mostly self explanatory, though in some spots I admit it might be a bit difficult to grasp.
Examples:
- generate a tree:
my_tree = CTree('node0.node00.node000') my_tree('node1.node10.node100')
my_tree('node0.node01.node010')
now here is an equivalent to the above generating only "node011"
my_tree.node0.node01('node011')
the following three lines are equivalent:
my_tree('node0.node01.node011')
my_tree.node0('node01.node011')
my_tree.node0.node01('node011')
- get a node by its path:
the following lines are equivalent and return the same node :)
my_tree('node1.node10.node100') my_tree.node1('node10.node100') my_tree.node1.node10('node100') my_tree.node1.node10.node100
Thanks and have fun!!