Search code examples
pythonpython-3.xscopespyderglobal-scope

Why won't this variable reference to a non-local scope resolve?


Here's an example to find the greatest common divisor for positive integers a and b, and a <= b. I started from the smaller a and minus one by one to check if it's the divisor of both numbers.

def gcdFinder(a, b):

    testerNum = a   

    def tester(a, b):
        if b % testerNum == 0 and a % testerNum == 0:
            return testerNum
        else:
            testerNum -= 1
            tester(a, b)

    return tester(a, b)

print(gcdFinder(9, 15))

Then, I got error message,

UnboundLocalError: local variable 'testerNum' referenced before assignment.

After using global testerNum, it successfully showed the answer 3 in Spyder console...

spyder's outcome

but in pythontutor.com, it said NameError: name 'testerNum' is not defined (link).

pythontutor's outcome

Q1: In Spyder, I think that the global testerNum is a problem since testerNum = a is not in global scope. It's inside the scope of function gcdFinder. Is this description correct? If so, how did Spyder show the answer?

Q2: In pythontutor, say the last screenshot, how to solve the NameError problem in pythontutor?

Q3: Why there's difference between the results of Spyder and pythontutor, and which is correct?

Q4: Is it better not to use global method?

--

UPDATE: The Spyder issue was due to the value stored from previous run, so it's defined as 9 already. And this made the global testerNum work. I've deleted the Q1 & Q3.


Solution

  • The Problem:

    When trying to resolve a variable reference, Python first checks in the local scope and then in the local scope of any enclosing functions. For example this code:

    def foo():
        x=23
        def bar():
            return x +1
        return bar
    
    print(foo()())
    

    Will run and print out 24 because when x is referenced inside of bar, since there is no x in the local scope it finds it in the scope of the enclosing function (foo). However, as soon as you try to assign to a variable, Python assumes it is defined in the local scope. So this:

    def foo():
        x=23
        def bar():
            x = x + 1
            return x
        return bar
    
    print(foo()())
    

    Will throw an UnboundLocalError because I am trying to assign to x which means it is going to be looked up in the local scope, but the value I'm trying to assign to it is based on the x from the enclosing scope. Since the assignment limits the search for x to the local scope, it can't be found and I get the error.

    So your error is coming up because of the testerNum -= 1 line in your else clause, it is limiting the search for testerNum to the local scope where it doesn't exist.

    The Fix:

    The global declaration isn't right since, as you noted, testerNum isn't defined in the global scope. I'm not familiar with Spyder and don't know why it worked there but it seems like it somehow got a testerNum variable in its global scope.

    If you're using Python3 you can get around this by changing your global testerNum line to nonlocal testerNum which just tells Python that, despite being assigned to, testerNum isn't defined in the local scope and to keep searching outwards.

    def foo():
        x=23
        def bar():
            nonlocal x
            x = x + 1
            return x
        return bar
    
    >>> print(foo()())
    24
    

    Another option that would work in Python 2 or 3 is to pass around testerNum as Brambor outlined in their answer.