【问题标题】:Recursive factorial using dict causes RecursionError使用 dict 的递归阶乘导致 RecursionError
【发布时间】:2016-04-14 21:18:56
【问题描述】:

一个简单的递归阶乘方法完美运行:

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

但我想尝试一下并改用dict。从逻辑上讲,这应该可行,但是一堆打印语句告诉我 n 并没有在 0 处停止,而是在负数上向下滑动,直到达到最大递归深度:

def recursive_fact(n):
    lookup = {0: 1}
    return lookup.get(n, n*recursive_fact(n-1))

这是为什么呢?

【问题讨论】:

    标签: python dictionary recursion


    【解决方案1】:

    Python 不会延迟计算参数。

    传递给dict.get调用的默认值也将在调用dict.get之前进行评估。

    因此,在您的情况下,默认值具有递归调用,并且由于您的条件从未满足,因此它会进行无限递归。

    您可以通过此程序确认这一点

    >>> def getter():
    ...     print("getter called")
    ...     return 0
    ... 
    >>> {0: 1}.get(0, getter())
    getter called
    1
    

    即使键 0 存在于字典中,由于将评估传递给 Python 中函数的所有参数,因此也会在生成实际的 dict.get 之前调用 getter


    如果您只想在已计算值时避免多次递归计算,则使用functools.lru_cache,如果您使用的是 Python 3.2+

    >>> @functools.lru_cache()
    ... def fact(n):
    ...     print("fact called with {}".format(n))
    ...     if n == 0:
    ...         return 1
    ...     return n * fact(n-1)
    ... 
    >>> fact(3)
    fact called with 3
    fact called with 2
    fact called with 1
    fact called with 0
    6
    >>> fact(4)
    fact called with 4
    24
    

    这个装饰器只是缓存传递参数的结果,如果再次进行相同的调用,它将简单地从缓存中返回值。


    如果你想修复你的自定义缓存函数工作,那么你需要在函数外部定义look_up,这样当函数被调用时它就不会被创建。

    >>> look_up = {0: 1}
    >>> def fact(n):
    ...     if n not in look_up:
    ...         print("recursing when n is {}".format(n))
    ...         look_up[n] = n * fact(n - 1)
    ...     return look_up[n]
    ... 
    >>> fact(3)
    recursing when n is 3
    recursing when n is 2
    recursing when n is 1
    6
    >>> fact(4)
    recursing when n is 4
    24
    >>> fact(4)
    24
    

    否则你可以使用默认参数,像这样

    >>> def fact(n, look_up={0: 1}):
    ...     if n not in look_up:
    ...         print("recursing when n is {}".format(n))
    ...         look_up[n] = n * fact(n - 1)
    ...     return look_up[n]
    

    【讨论】:

    • 哦,我明白了。因此,即使满足第一个条件,也会评估 default= 参数。似乎有点违反直觉,但至少我的问题解决了。谢谢!
    • @shooqie 实际上,在调用.get 函数本身之前,Python 应该知道要传递的实际值。因此,它将评估作为参数传递的所有表达式。在您的情况下,其中一个表达式恰好是递归调用,它只是在调用 .get 之前做到了这一点
    • @shooqie 你有函数式编程背景吗?从命令的角度来看,必须在调用函数之前评估所有参数。
    • @Jasper 这个问题是给我的吗?由于 OP 预计默认参数在使用之前不会被评估,因此我假设 OP 熟悉其他语言中参数的惰性评估。
    • 现在已正确标记,我在问 OP。
    猜你喜欢
    • 2017-04-19
    • 1970-01-01
    • 1970-01-01
    • 2016-01-28
    • 1970-01-01
    • 2015-04-19
    • 2019-07-24
    • 2013-09-18
    • 2012-01-01
    相关资源
    最近更新 更多