Python Recursion through objects and child objects, Print child depth numbers

I have a simple class with an attribute that can contain a list of objects of the same class

class BoxItem:
  def __init__(self, name, **kw):
      self.name = name
      self.boxItems = []
      ... #more attributes here

box1 = BoxItem('Normal Box')
box2 = BoxItem('Friendly Box')
box3 = BoxItem('Cool Box')
box4 = BoxItem('Big Box', [box1, box2]) #contains some children
example = BoxItem('Example Box', [box4,box3]) #contains another level of children

Working with our ‘example’ box object, I would like to maneuver through the depths of all possible box children it may have, and print out the objects formatted like so:

1 Example Box
 1.1 Big Box
  1.1.1 Normal Box
  1.1.2 Friendly Box
 1.2 Cool Box

The Tabbing between isn’t needed, just wanting to show the tree format clearly. I am able to walk down the objects themselves and print out their titles, but I am not able to print out the front numbers that show the parent/child relationship. (1, 1.1, 1.2…)

Thanks in advance for your help 🙂

Edit
Here is what I have been working with so far

def print_boxes(box_list):
  node_count = 0
  for box in box_list:
    node_count += 1
    print str(node_count)+' '+box.name #prints out the root box
    recursive_search(box,node_count)

 def recursive_search(box,node_count): #recursive automatically 
  level = 0
  for child in box.boxItems:
    level += 1
    for x in range(len(child.boxItems)):
      print x+1 #this prints out some proper numbers
    print "level: "+str(level) #experiment with level
    print child.name #prints out child box name
    recursive_search(child,node_count) #runs the function again  inside the function

Best answer

I think it might be more helpful to you if I post a working example of how to do this, as opposed to going through where you code is having problems. We might get to the point of understanding a lot faster that way. Your code has the correct idea that it needs to track the depth as it goes. But the only thing it is missing is a sense of nested depth (tree). It only knows the previous node_count, and then its current child count.

My example uses a closure to start the depth tracking object, and then creates an inner function to do the recursive part.

def recurse(box):

    boxes = not isinstance(box, (list, tuple)) and [box] or box

    depth = [1]

    def wrapped(box):

        depthStr = '.'.join([str(i) for i in depth])
        print "%s %s" % (depthStr, box.name)

        depth.append(1)
        for child in box.boxItems:
            wrapped(child)
            depth[-1] += 1
        depth.pop()

    for box in boxes:
        wrapped(box)
        depth[0] += 1

Sample output from your examples:

>>> recurse(example)
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box

>>> recurse([example, example])
1 Example Box
1.1 Big Box
1.1.1 Normal Box
1.1.2 Friendly Box
1.2 Cool Box
2 Example Box
2.1 Big Box
2.1.1 Normal Box
2.1.2 Friendly Box
2.2 Cool Box

Breaking this down:

We first accept a box argument, and automatically convert it locally to a list, if you had only passed in a single box item. That way you can pass either one box objects, or a list/tuple of them.

depth is our depth tracker. Its a list of ints that we will build up and shrink down as the recursion happens. It starts at 1 for the first item / first level. Over time it can look like this: [1,1,2,3,1] depending on how deep it traverses. This is the major difference between my code and yours. Each recursion has access to this state.

Now we have this inner wrapped function. Its going to take a current box item and print it, and then iterate over its children. We get our print string by joining the current depth list, and then the name.

Every time we drop down into a child list, we add a starting level 1 to our depth list, and when we come out of that child loop, we pop it back off again. For every child in that loop, we increment that last item up.

Outside of that wrapped inner function, we then start the whole thing by looping over our initial boxes, calling wrapped and then incrementing our first level.

The inner wrapped function uses the depth list in a closure. I am willing to bet that others can offer some further improvements on this, but its what I came up with for an example.

Note about the args for the function

We could have also designed recurse to instead take a variable length argument list, instead of checking for a list. It would look like this (and would get rid of that first boxes = check):

def recurse(*boxes):
    #boxes will always come in as a tuple no matter what

>>> recurse(example)
>>> recurse(example, example, example)

And if you originally start with a list of box items, you can pass it by doing:

>>> boxes = [example, example, example]
>>> recurse(*example)    # this will unpack your list into args