在递归函数中返回中间值是python的怪癖吗?

时间:2018-11-13 21:19:50

标签: python python-3.x recursion

更新: 让我澄清一下这到底是什么令人困惑。 如果我添加如下打印声明:

    def recur(lis, json, target):
        if lis[0] == target:
            print(json[tagert])
            return json[target]
        else:
            recur(lis[1:], json[lis[0]], target)

我的打印语句将显示JSON的期望值。我不知道该怎么说。鉴于return语句之前的行给出了我期望的结果(else中没有return语句),为什么else中的return语句是必需的?

对于那些坚持反对这一观点的人,我已经多次询问过有关遗失申报表的问题。在这些问题中从未回答过的是,为什么需要退货。拒绝我想要的所有内容,但至少了解您正在拒绝一个好问题。我以为这个社区正在成熟,但显然没有。


所以我看了几个与我的标题相同的问题,但我仍然不太明白为什么会这样。

如果我有这样的递归函数:

def recur(lis, json, target):
    if lis[0] == target:
        return json[target]
    else:
        return recur(lis[1:], json[lis[0]], target)

我得到了预期的回报。

但是,如果我不返回else语句,则会得到一个None:

def recur(lis, json, target):
    if lis[0] == target:
        return json[target]
    else:
        recur(lis[1:], json[lis[0]], target)
>>> final_json = recur(my_list, my_json, 'ID')
>>> print(final_json)   
None

这是特定于Python的吗?我有点生疏,但我似乎记得像Haskell这样的语言可以更优雅地处理此问题,我相信,我不需要返回递归调用的值。这对我来说更有意义-我不需要所有中间值,因为我在栈的每个级别传递函数所需的所有值。我在这里想念什么?

4 个答案:

答案 0 :(得分:5)

我能想到的最好的方法就是用几种不同的语言比较一个非常简单的递归递归函数。我选择的函数将计算整数的阶乘。 (非负数,为简单起见,如果出现负整数,浮点数或愚蠢的东西,我将不尝试执行任何验证来阻止函数爆炸。)

首先,在Python中:

def factorial(n):
    if (n == 0):
        return 1
    return n * factorial(n-1)

因此,这里您具有“返回的中间值”,这似乎是您声称的似乎对Python唯一的东西。 (当然,您不是返回递归调用本身的结果,而是对其执行简单的操作-但这不会改变这种情况,除非我完全误解了 。您正在仍返回值,以便对该“中间结果”进行处理。)

因此,让我们看一下如何在Javascript中执行相同的操作。 (是的,两种语言都有更优雅的方法来做到这一点,但我正在努力使事情变得简单且严格可比。)

function factorial(n) {
    if (n == 0) {
       return 1;
    }
    return n * factorial(n-1);
}

我希望您会同意,撇开基本语法的琐碎差异,JS版本与上述Python版本相同。尤其是,两者都可以“返回中间值”。

我可以用PHP编写完全相同的东西,或者(尽管我不太熟悉那些语言),但我想用C / C ++ / C#/ Java编写,而且还是一样。

现在,如果我们终于来了Haskell,它确实是一种与上述所有语言完全不同的语言,让我们看一下如何定义相同的功能:

factorial :: Integer -> Integer
factorial n
    | n==0 = 1
    | otherwise = n * factorial (n-1)

是的,这里没有明确的return语句。但这仅仅是因为Haskell函数是“纯”函数,必须总是产生一个值,所以在一些更复杂的代码的末尾,没有显式的声明来告诉您该值是什么,您只需在每个可能的输入上定义其结果。

当然,您可以并且经常使用组合和其他高阶运算以“无点”样式更抽象地定义函数-这是函数编程的好处之一。但是归根结底,在Haskell中,函数最终是根据给定输入的输出结果来定义的-这实际上是基础,这就是“函数”一词在数学中的含义,以及在数学中的含义。纯功能语言。 (与单纯的“过程”相反,可重用的代码块可能会或可能不会导致值,就像大多数过程语言(如JS,Python和其他语言)一样。)

因此,换句话说,上面的 still “返回中间值”。示例最后一行中的=符号用其他语言完成了return语句的工作。

因此,如果我对一个非常简单的话题进行了太长时间了,我深表歉意-我仍然不确定你的困惑所在。但是我希望这可以帮助您克服它。

答案 1 :(得分:4)

>>> final_json = recur(my_list, my_json, 'ID')
>>> print(final_json)   
None
  

如果我不返回else语句,则会得到一个None…我不需要所有中间值,因为我正在向函数传递其在堆栈每个级别所需的所有值。我在这里想念什么?

在函数调用final_json = recur(...)中,您指出final_jsonrecur 返回的任何内容。

如果这是您的实现:

def recur(lis, json, target):                # line 1
    if lis[0] == target:                     # line 2
        print(json[tagert])                  # line 3
        return json[target]                  # line 4
    else:                                    # line 5
        recur(lis[1:], json[lis[0]], target) # line 6

recur(myArg1, myArg2, myArg3)                # line 7

然后仔细考虑如果您在recur(someLis, someJson, someTarget)不等于lis[0]的情况下致电target会发生什么情况。

在伪堆栈跟踪中:

RECUR CALL A
├─ A condition fails (line #2)
├─ A calls `recur` (l#6)
│    └─ RECUR CALL B
│       ├─ B condition fails (#2)
│       ├─ B calls `recur` (#6)
│       │    └─ RECUR CALL C
│       │       ├─ C condition passes (#2)
│       │       ├─ C prints answer (#3)
│       │       └─ C returns answer to B (#4)
│       ├─ B receives answer from C (#6)
│       └─ B returns `None` (because you didn't use `return`) (#6)
├─ A receives `None` from B (#6)
└─ A returns `None` (because you didn't use `return`) (#6)

除非最外面的recur调用恰好是在基本情况下(通过条件if lis[0]...),否则相关的行是第6行,在该行中您没有return关键字。

是的,您在第3行和第4行计算了最终答案。

是的,您return是第4行的答案。

但是return是用于第6行的呼叫!不是顶层呼叫(第7行)。

因此,您最终回到了第6行,已经计算出所需的答案,并且……您什么也不做。您不需要return。没事。

最终,堆栈展开到顶级调用(第7行),该调用接收默认的Python返回值(None),这是运行的函数的最后一行(第6行)的结果,因为该行没有return语句。

问题不是Haskell“不需要返回中间调用”。实际上恰恰相反。 Haskell 自动返回其功能主体。在Haskell 1 中没有明确的return关键字,因为您总是隐式返回某些内容,而无需记住自己键入内容。

以下是一个等效示例:

def ret5 ():
    return 5

def example ():
    return ret5()

def final ():
    example()

如果您致电result = final(),那么result显然将是None,对吗? finalexample没关系,return给出了您想要的答案。由于final并非return的结果,因此即使您在编程过程中的某个时刻“达到”了答案,顶层输出仍然是example执行。


1 不幸的是,Haskell中有一个名为 None的函数,该函数与从函数返回值无关。这是一个可悲的名称选择,大概是为了使return-符号看起来更必要。因此,我更喜欢相同的功能do

答案 2 :(得分:2)

如果我们将递归从图片中删除,可能会更容易理解。这是一个类似的结构:

def funcA(lis, json, target):
    return json[target]

def funcB(lis, json, target):
    return funcA(lis[1:], json[lis[0]], target)

>>> final_json = funcB(my_list, my_json, 'ID')
>>> print(final_json)

在这里,我们可以遵循执行路径:

  • 我们调用funcB并传递一些参数。
  • funcB依次调用funcA并传递一些参数。
  • funcA将值返回给funcB
  • 该值由funcB返回。
  • funcB返回的值分配给final_json

现在,您的问题是,如果我们将funcB重新定义为:为什么它会停止工作?

def funcB(lis, json, target):
    funcA(lis[1:], json[lis[0]], target)

通过此重新定义,以及其他所有相同的内容,执行路径更像:

  • 我们调用funcB并传递一些参数。
  • funcB依次调用funcA并传递一些参数。
  • funcA将值返回给funcB
  • funcB会忽略该值。
  • funcB结束而不返回值,因此默认情况下它返回None
  • funcB返回的值分配给final_json

由于funcB从未显式返回它从funcA获得的值,因此默认情况下它返回None


由于递归函数仅仅是调用自身的函数,因此其行为与调用其他函数的函数没有什么不同。

这是您的功能:

    def recur(lis, json, target):
        if lis[0] == target:
            return json[target]
        else:
            return recur(lis[1:], json[lis[0]], target)

>>> final_json = funcB(my_list, my_json, 'ID')
>>> print(final_json)

让我们经历与上述相同的过程,假设它将重复一次:

  • 我们调用recur并传递一些参数。
  • recur依次再次调用recur并传递一些参数。
  • recur的第二次调用将值返回到第一次调用。
  • 第一次调用recur会获得该值并将其返回给我们。
  • 第一次调用recur返回的值已分配给final_json

但是,如果我们删除return

    def recur(lis, json, target):
        if lis[0] == target:
            return json[target]
        else:
            recur(lis[1:], json[lis[0]], target)
  • 我们调用recur并传递一些参数。
  • recur依次再次调用recur并传递一些参数。
  • recur的第二次调用将值返回到第一次调用。
  • recur的第一次调用将获取该值并忽略它。
  • recur的第一次调用在不返回值的情况下结束,因此默认情况下它将返回None
  • 第一次调用recur返回的值已分配给final_json

同样,由于第一次调用recur从未显式返回从第二次调用recur得到的结果,因此默认情况下它返回None


  

更新:让我澄清一下这到底是什么令人困惑。如果我添加如下打印声明:

def recur(lis, json, target):
    if lis[0] == target:
        print(json[tagert])
        return json[target]
    else:
        recur(lis[1:], json[lis[0]], target)
     

我的打印语句将显示JSON的期望值。我不知道该怎么说。鉴于return语句之前的行给出了我期望的结果(else中没有return语句),为什么else中的return语句是必需的?

print是全局的,它写入stdout而不考虑函数深度或堆栈帧。另一方面,return存在于函数级别,并且仅影响单个函数调用返回的值。如果该函数调用对其调用的函数的返回值不执行任何操作,则该值将丢失。


  

这是特定于Python的吗?我有点生疏,但我似乎还记得Haskell这样的语言可以更优雅地处理此问题,我相信,我不需要返回递归调用的值。

Haskell函数仅定义一个值;不需要显式的return,因为该函数的整个主体都是隐式的。其他语言(例如Lisp家族)总是返回块中最后一行的值,因此在这种情况下也不需要显式return

请注意,在这两种情况下,return语句中的会丢失,而不仅仅是第二个。一般而言,这大约是return,与递归调用无关。

如果要返回任何有用的值,Python和大多数其他主流语言都需要显式的return

答案 3 :(得分:0)

这并非特定于Python;它特定于所有C类和C类相邻语言(也许所有命令式语言),并且按流行程度加权,Haskell是奇怪的一种。在C,C ++,Python,Perl,PHP等中,格式为return expression的语句从该函数返回给定的表达式,而仅expression对该表达式求值并将其丢弃而不返回它。只有在功能更强的语言(例如Haskell和Scheme)中,函数定义往往只有一个语句,“返回”是隐式的。