为什么这个变量无法引用非本地作用域?

3
这是一个查找正整数 ab 的最大公约数的示例,其中 a <= b。我从较小的 a 开始逐个减去,以检查它是否是两个数字的约数。
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))

然后,我收到了错误信息, UnboundLocalError: local variable 'testerNum' referenced before assignment
在使用global testerNum之后,它成功地在Spyder控制台中显示了答案3...

spyder's outcome


但在pythontutor.com上,它显示NameError: name 'testerNum' is not definedlink)。

pythontutor's outcome

问题1: 在Spyder中,我认为global testerNum是一个问题,因为testerNum = a不在全局作用域内。它在函数gcdFinder的作用域内。这个描述正确吗?如果是,Spyder是如何显示答案的?

问题2: 在pythontutor中,比如最后一张截图,如何解决NameError问题?

问题3: 为什么Spyder和pythontutor的结果不同,哪个是正确的?

问题4: 不使用global方法是否更好?

--

更新:Spyder问题是由于之前运行中存储的值所致,因此它已被定义为9。这使得global testerNum起作用。我已删除Q1和Q3。

不回答你的任何问题,我认为最好将testerNum解析为参数,这样函数“ tester”就可以定义为:“def tester(a,b,testerNum)”。 - Brambor
实际上这是你第2和第4个问题的答案 :D - Brambor
1
在样例代码中,testNum 不是 global 的。尝试使用关键字 nonlocal 代替。这假定你正在使用 Python3。 - hcoat
3个回答

5

问题:

在解析变量引用时,Python 首先会检查本地作用域,然后是任何包含函数的本地作用域。例如,以下代码:

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

print(foo()())

当在bar函数内部引用x时,会运行并打印出24。因为在局部范围内没有x变量,因此会在包围函数(foo)的作用域中找到它。然而,只要你尝试分配一个变量,Python就会假定它定义在局部范围内。所以这个:

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

print(foo()())

因为我试图对x进行赋值,这意味着它将在局部作用域中查找,但我尝试分配给它的值是基于来自封闭作用域的x。由于该赋值限制了对x的搜索范围仅限于局部作用域,因此无法找到并出现了错误"UnboundLocalError"。
所以你的错误是由else子句中的"testerNum -= 1"行引起的,它将对testerNum的搜索范围限制在局部作用域中,而在那里它不存在。
修复方法:
global声明不正确,因为如你所指出,testerNum没有在全局作用域中定义。我不熟悉Spyder,也不知道为什么它会在全局作用域中工作,但似乎它在全局作用域中获得了一个testerNum变量。
如果你使用Python3,你可以通过将"global testerNum"行更改为"nonlocal testerNum"来避免这种情况,它只是告诉Python,尽管被分配了,testerNum在局部作用域中未定义,并继续向外搜索。
def foo():
    x=23
    def bar():
        nonlocal x
        x = x + 1
        return x
    return bar

>>> print(foo()())
24

另一个可用于 Python 2 或 3 的选项是按照 Brambor 在其答案中概述的方式传递 testerNum

谢谢,解释得很清楚,特别是第三段。 - chenghuayang
所以我可以说,在函数外部调用变量是可以的,但如果我想对其进行更改,那是行不通的。 - chenghuayang
1
@chenghuayang 正确,除非你声明它为 nonlocal - Turn
nonlocal也不推荐使用吗?它会要求计算机在外部进行搜索,这会消耗一定的处理能力。 - chenghuayang
2
@chenghuayang,它不会消耗任何你能注意到的处理能力。至于是否是不好的形式,这肯定是一个观点问题。我认为在某些情况下它可能会有用,但它确实会引起一些混淆,而且通常最好避免使用。 - Turn

2

第二个问题(Q2)和第四个问题(Q4)的答案。

正如我在评论中所写,您可以将testerNum解析为参数。

然后您的代码将如下所示:

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))

编辑:(请参见评论)


这解决了问题。谢谢。但是如果我在函数外设置一个变量并将其作为参数设置在函数内,这会很奇怪吗? - chenghuayang
1
我不太确定你的意思。如果您关注的是代码 a = 5; def double(x): return x*2; print(double(a)),那么是否涉及到在您的代码中重叠使用 xa,那么并不奇怪。 - Brambor
好的,明白了。最好使用不同的词语。 - chenghuayang

0

如果只是为了解决问题4,最好根本不要使用globalglobal通常突出了糟糕的代码设计。

你正在做很多工作;我强烈建议你查找标准计算GCD的方法,并实现欧几里得算法。 编码细节留给学生自己练习。


感谢您的建议和信息。欧几里得算法确实是计算最大公约数的好方法。 - chenghuayang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接