3

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.

Turn
  • 6,656
  • 32
  • 41
chenghuayang
  • 1,424
  • 1
  • 17
  • 35
  • not answer to any of your questions, I think it would be better to parse the testerNum as parametr so function `tester` would be defined: `def tester(a, b, testerNum)` – Brambor Feb 01 '18 at 17:23
  • actually it is answer to your questions 2 and 4 :D – Brambor Feb 01 '18 at 17:25
  • 1
    In the sample code `testNum` is not `global`. Try using the key word `nonlocal` instead. This assumes that you are using python3. – hcoat Feb 01 '18 at 17:46

3 Answers3

5

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.

Turn
  • 6,656
  • 32
  • 41
  • Thanks, pretty clear explanation, especially the 3rd paragraph. – chenghuayang Feb 01 '18 at 18:15
  • So I can say, it's okay to call a variable outside the function, but if I want to make change of it, that's not gonna work. – chenghuayang Feb 01 '18 at 18:17
  • 1
    @chenghuayang Correct, unless you can declare it `nonlocal`. – Turn Feb 01 '18 at 18:17
  • Is `nonlocal` not recommended either? It asks computer to search outside and this consume somewhat processing. – chenghuayang Feb 01 '18 at 18:31
  • 2
    @chenghuayang It isn't going to consume any amount of processing power you'll ever notice. As to whether it is bad form, that is certainly going to be a matter of opinion. I think in some cases is can be useful but it can definitely cause some confusion and it is probably safer to avoid as a general practice – Turn Feb 01 '18 at 18:37
2

Answers to Q2 and Q4.

As I wrote in the comments, you can parse the testerNum as parameter.

Your code would then look like this:

def gcdFinder(a, b):

    testerNum = a   

    def tester(a, b, testerNum):
        if b % testerNum == 0 and a % testerNum == 0:
            return testerNum
        else:
            testerNum -= 1
            return tester(a, b, testerNum)  # you have to return this in order for the code to work

    return tester(a, b, testerNum)

print(gcdFinder(9, 15))

edit: (see coment)

Brambor
  • 604
  • 1
  • 8
  • 25
  • This solved the problem. Thanks. But is it weird if I set a variable outside the function and also set it as a parameter within the function? – chenghuayang Feb 01 '18 at 17:44
  • 1
    I'm not exaxtly sure what do you mean. If your concern is regarding code `a = 5; def double(x): return x*2; print(double(a))`. If it is overlaping `x` and `a` in your code, then no, it is not wierd – Brambor Feb 01 '18 at 17:52
  • OK, got it. Just better to use different word. – chenghuayang Feb 01 '18 at 18:01
0

TO hit only question 4, yes, it's better not to use global at all. global generally highlights poor code design.

You're going to a lot of trouble; I strongly recommend that you look up standard methods of calculating the GCD, and implement Euclid's Algorithm instead. Coding details left as an exercise for the student.

Prune
  • 76,765
  • 14
  • 60
  • 81
  • Thanks for the suggestion and info. [Euclid's Algorithm](https://www.khanacademy.org/computing/computer-science/cryptography/modarithmetic/a/the-euclidean-algorithm) is really a good way to calculate GCD. – chenghuayang Feb 01 '18 at 17:48