Python中的变量作用域问题

3

我刚接触Python不久,已经在使用它一段时间了,但现在卡在一个问题上。这是我的代码:

def collatz(num,ctr):
    if(num != 1):
        ctr+=1
        if(num%2==0):
            collatz(num/2,ctr)
        else:
            collatz(num*3+1,ctr)
    return ctr
test=collatz(9,0)

对于我输入的任何数字,比如说,num9ctr0,那么无论如何,ctr 的值始终为 1。我是不是在错误地使用了 ctr 变量?
编辑: 我试图打印出函数递归的次数。因此,ctr 将是每次递归的计数器。

你的预期结果是什么?你想通过这个实现什么目标? - harshil9968
ctr = collatz(num // 2, ctr)。同时,您应该使用Python 3,并且“/ 2”是浮点除法,“// 2”是整数除法。 - Antti Haapala -- Слава Україні
5个回答

4

我将您的递归调用更改为将从递归调用返回的值设置为ctr。以您写的方式,您正在丢弃从递归中获得的值。

def collatz(num,ctr):
    if(num != 1):
            ctr+=1
            if(num%2==0):
                    ctr=collatz(num/2,ctr)
            else:
                    ctr=collatz(num*3+1,ctr)
    return ctr

test=collatz(9,0)

这是一样的,有什么区别吗? - harshil9968
1
我将你的递归调用更改为将递归调用返回的值设置为“ctr”。按照你的写法,你正在丢弃从递归中返回的值。 - Robert Columbia
2
你应该在回答中解释清楚,而不仅仅给出代码。 - OneCricketeer

3
你的示例中变量ctr始终为1,因为递归调用栈的顺序。当一个值ctr被返回时,调用栈将开始返回ctr的先前值。基本上,在最后一个递归调用中,ctr的最高值将被返回。但由于调用栈底部的方法调用返回最后一个值,即将存储在test中的值,test将始终为1。假设我向collatz输入参数,这将导致该方法总共调用五次。调用栈会像这样下降,
collatz returns ctr --> 5
collatz returns ctr --> 4
collatz returns ctr --> 3
collatz returns ctr --> 2
collatz returns ctr --> 1 //what matters because ctr is being returned with every method call

正如您所看到的,无论调用多少次“collatz”,最终都会返回1,因为调用栈底部的调用具有“ctr”等于1。
解决方案可以有很多种,但实际上取决于您试图实现的目的,而这在您的问题中并没有明确说明。
编辑:如果您希望“ctr”最终成为递归调用的次数,则只需将“ctr”赋值为方法调用的值。应该像这样:
def collatz(num,ctr):
    if(num != 1):
        ctr+=1
        if(num%2==0):
            ctr = collatz(num/2,ctr)
        else:
            ttr = collatz(num*3+1,ctr)
    return ctr
test=collatz(9,0)

谢谢你解释递归的工作原理。我现在理解得更好了。我不太擅长解释我想要的最终结果,所以我会编辑问题。 - Webtron
@Webtron,看一下我的修改,因为我认为它现在应该解决了你的问题。 - Chris Gong

0

一个例子:

def collatz(number):

    if number % 2 == 0:
        print(number // 2)
        return number // 2

    elif number % 2 == 1:
        result = 3 * number + 1
        print(result)
        return result

n = input("Give me a number: ")
while n != 1:
    n = collatz(int(n))

不完全符合提问者的要求。他的函数试图计算在达到1之前需要运行多少次。 - Sepehr Nazari

0
在Python中,函数参数是按值传递的,而不是按引用传递的。如果你把一个数字传递给一个函数,函数会接收到该数字的一份副本。如果该函数修改了其参数,那么这个改变在函数外部是不可见的。
def foo(y):
   y += 1
   print("y=", y) # prints 11

x = 10
foo(x)
print("x=", x) # Still 10

在你的情况下,最直接的解决方法是将 ctr 变成全局变量。这很丑陋,因为如果你想再次调用 collatz 函数,你需要将全局变量重置为 0,但我展示这个替代方案只是为了说明你的逻辑是正确的,除了传递引用的部分。(请注意,collatz 函数现在不返回任何内容,答案在全局变量中)。
ctr = 0
def collatz(num):
    global ctr
    if(num != 1):
        ctr+=1
        if(num%2==0):
                collatz(num/2)
        else:
                collatz(num*3+1)

ctr = 0
collatz(9)
print(ctr)

由于Python没有尾递归优化,如果Collatz序列超过1000步(这是Python默认的堆栈限制),则当前的递归代码将导致堆栈溢出。您可以通过使用循环而不是递归来避免此问题。这还可以让我们摆脱那个麻烦的全局变量。在我看来,最终结果更符合Python语言的习惯用法:

def collats(num):
    ctr = 0
    while num != 1:
        ctr += 1
        if num % 2 == 0:
            num = num/2
         else:
            num = 3*num + 1
    return ctr

print(collatz(9))

如果您想坚持使用递归函数,通常最好避免使用可变赋值,就像您试图做的那样。而不是将函数作为修改状态的“子程序”,将它们转换成更接近数学函数的东西,这些函数接收一个值并返回仅取决于输入的结果。如果您这样做,递归可能会更容易理解。我将把这留作练习,但递归函数的典型“框架”是具有检查基本情况和递归情况的if语句:

def collatz(n):
    if n == 1:
        return 0
    else if n % 2 == 0:
        # tip: something involving collatz(n/2)
        return #??? 
    else:
        # tip: something involving collatz(3*n+1)
        return #???

-3

这个变量将返回从任何数字开始的Collatz序列的最终数字。Collatz猜想说这个数字总是1。


从数学上讲,是的,但这并不是该方法返回1的原因。 - OneCricketeer
该函数应返回到达1所需的步骤数。 - chepner

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