0
def node_count(tree):
   if is_leaf_node(tree):
      return 0
   count = 1
   def inc_count(node): #node argument not used her, but needed in call
      count += 1
  tree_traversal(tree, inc_count)
  return count

tree_traversal applies a function to each node in the tree and works fine. This method gives me: UnboundLocalError: local variable 'count' referenced before assignment

But this works:

 def node_count(tree):
    if is_leaf_node(tree):
       return 0
    l = []
    def inc_count(node): #node argument not used her, but needed in call
       l.append(1) #Add whatever
    tree_traversal(tree, inc_count)
    return len(l)

Why?

The last method works, but looks strange. Any other ways of doing it?

ErikN
  • 19
  • 2
  • Briefly: in the first code block, you are doing an assignment. In the second, you are mutating a mutable object. If you attempted to reassign the `list` in the second code block, you'd see similar behavior. You can read more in the linked duplicate. – TigerhawkT3 Apr 24 '16 at 04:14

3 Answers3

2

The first is assigning count to count + 1. Since you are using the same variable name on both sides of =, Python assumes that you are talking about the same object in both cases. count, however, was not declared global (or nonlocal in Python 3), so you are trying to assign the local count to the local count + 1. Problem: there is no such thing as a local count yet. The solution (in Python 3):

def node_count(tree):
   if is_leaf_node(tree):
      return 0
   count = 1
   def inc_count(node): #node argument not used her, but needed in call
      nonlocal count
      count += 1
  tree_traversal(tree, inc_count)
  return count

There doesn't seem to be a nice, easy solution in Python 2, but you can at least use an actual number in the list instead of finding the sum:

def node_count(tree):
   if is_leaf_node(tree):
      return 0
   count = [1]
   def inc_count(node): #node argument not used her, but needed in call
      count[0] += 1
  tree_traversal(tree, inc_count)
  return count[0]
zondo
  • 19,901
  • 8
  • 44
  • 83
0

If you are using Python3, try to use nonlocal:

def node_count(tree):
    if is_leaf_node(tree):
        return 0
    count = 1
    def inc_count(node): #node argument not used her, but needed in call
        nonlocal count
        count += 1
    tree_traversal(tree, inc_count)
    return count

For Python2, you can reference to PEP 3104 - Access to Names in Outer Scopes

dokelung
  • 206
  • 1
  • 5
0

In python, non-local variables are allowed to be implicitly used, but only for reading.

That is, in your code:

count = 0
def inc_count(node):
    count += 1

The second use of count is a non-local reference that would be allowed if you were not trying to modify it. But modification of non-local symbols is considered a source of error, so you are required to explicitly acknowledge that you know it's not local.

In python 3, you can use the nonlocal keyword for this purpose, or the global keyword for globals. In python 2, only the global keyword was available.

A list is a funny thing, though. You can't modify the list, but you can read it. Since a list is really a pointer to an underlying object, you can "read" it and then use an operator such as [] on its value. This appears to work around the no-modification-without-declaration rule.

If you're doing python 2, and don't have the nonlocal statement, you might consider doing something like count = [0] and then later count[0] += 1.

aghast
  • 14,785
  • 3
  • 24
  • 56
  • You certainly *can* modify the list. Assignments and modifications are two completely different operations. – zondo Apr 24 '16 at 04:14
  • Which was exactly my point: you can read it and then use [] on it. – aghast Apr 24 '16 at 05:43
  • Wording is important when you are teaching beginners. The list can be modified (such as `.append()` or `.extend()`), but it cannot be reassigned. – zondo Apr 24 '16 at 10:32