【问题标题】:Python: decorating simple recursive functionPython:装饰简单的递归函数
【发布时间】:2014-08-31 21:45:05
【问题描述】:

我想练习递归和装饰器并尝试做这个简单的功能,但它不起作用:

 def dec(func):

     def wrapper(number):
         print("Recursive count:")
         rec_cou(number)

     return wrapper

 @dec
 def rec_cou(number):
     """ Count from 0 to a given number from 50 and up """

     if number == 0:
         print(number)
         return number
     num = rec_cou(number - 1)
     print(num + 1)
     return num + 1

 rec_cou(53)

单独的递归函数效果很好,但是当我添加装饰器时会产生错误:超出最大递归深度

【问题讨论】:

  • ....出于好奇,你为什么在这里使用装饰器?
  • @KyleStrand 只想在每次调用 rec_cou 时只打印一次文本“递归计数:”。我现在知道如何在装饰器中创建一个新属性。但是最好知道一个更简单的方法。
  • 我会说装饰器根本不是正确的语言结构。只需在递归函数中移动“第一次”标志即可。
  • 我不明白你的第一次标志是什么意思。
  • 这是指示函数是否第一次被调用的标志(即在递归下降之前)。在乔尔的回答中,它是成员wrapper.initial

标签: python function recursion decorator


【解决方案1】:

你的装饰器有两个问题:

  1. 您尝试调用 decorated 函数,在装饰器内部再次有效地调用包装函数,因此您有一个无限递归循环;改为调用原始函数func
  2. 在外部看来,装饰函数的行为应该与原始函数一样,特别是它应该返回其结果;否则你会在尝试添加数字和None时遇到类型错误

另外,目前你的装饰器没有计算任何东西......试试这个:

def dec(func):
    func.count = 0          # give each decorated function its own counter
    def wrapper(number):
        print("Recursive count: %d" % func.count)
        func.count += 1     # increase counter
        return func(number) # call original function 'func' and return result
    return wrapper

更新:从您的 cmets 看来,我似乎误解了您的装饰器应该做什么,而您误解了装饰器的工作方式。当您第一次调用函数时,装饰器不会被调用一次,但它会将函数替换为在装饰器中定义的函数。换句话说,

@dec
def foo(...):
    ...

等价于

def foo(...):
   ...
foo = dec(foo)

即装饰器在函数被装饰时只调用一次,并且每次调用原始函数时都会调用装饰器中构造的函数,并替换它。如果您只想打印一次,请使用另一个答案中的装饰器,或者根本不使用装饰器:只需创建一个打印然后调用函数的包装器。这对于为递归函数提供“入口点”并不罕见。

def print_and_run(number):
    print("Recursive count:")
    rec_cou(number)

顺便说一句,这是我通常用来可视化递归调用的装饰器:

def trace(f):
    trace.depth = 0
    def _f(*args, **kwargs):
        print "  " * trace.depth, ">", f.__name__, args, kwargs
        trace.depth += 1
        res = f(*args, **kwargs)
        trace.depth -= 1
        print "  " * trace.depth, "<", res
        return res
    return _f

【讨论】:

  • 此外,将print(number) 行移到递归调用上方可能是个好主意。否则,您将获得所有 53 个“递归计数:”输出,然后是所有 53 个数字,而不是每个数字都与“递归计数:”的每次打印一起出现。
  • @BrenBarn 我试过了,但没用。我还有 53 个“递归计数”。我只是希望装饰器在每次调用该函数时打印一次该文本。不是在应用装饰器时。仍然不知道热这样做。随着您的修改,变化是没有打印 0。
  • @DmitryIvanov 查看我的更新。好像有什么误会,你需要的可能根本就不是装饰师。
  • 创建一个新的包装器看起来更简单,但我必须调用那个包装器。我想直接调用递归函数,文本只打印一次。感谢您对装饰器和示例的解释。我很难理解它们。
【解决方案2】:

要解决最大递归深度问题,调用传入装饰器的函数(func)而不是rec_cou,并返回函数调用的值。也就是说,在第 5 行,将 rec_cou(number) 替换为 return func(number)

编辑:

def decorate(function):
    def wrapper(parameter):
        if wrapper.initial:
            print("Recursive count:")
            wrapper.initial = False
        result = function(parameter)
        wrapper.initial = True
        return result

    wrapper.initial = True
    return wrapper

@decorate
def count(number):
    """ Prints integers on the interval [0, number] """
    if number:
        count(number - 1)
    print(number)

count(53)

没有装饰器:

def count(number):
    """ Prints integers on the interval [0, number] """
    if number:
        count(number - 1)
    else:
        print("Recursive count:")
    print(number)

count(53)

【讨论】:

  • 没错,那是我的错误。但我希望在调用函数时只显示一次“递归计数:”文本。这样“递归计数:”打印了 53 次。
  • @DmitryIvanov 我明白了。我将添加一些执行此操作的代码。
  • 很好的解决方案。有没有其他更简单的方法来做同样的事情,在调用函数时只打印一次该文本。也许没有装饰器?
  • @DmitryIvanov 是的。我添加了另一个示例。
  • 完美,正是我想要的。
【解决方案3】:

如果您只想让函数rec_cou 在其递归下降之前打印一些东西,只需修改该函数,不要打扰装饰器。

def rec_cou(number, internal_call=False):
     """ Count from 0 to a given number from 50 and up """
     if not internal_call:
         print "Now performing recursive count, starting with %d" % number
     if number == 0:
         return number
     num = rec_cou(number - 1, internal_call=True)
     return num + 1

正如我在 cmets 中提到的,我所做的只是采用 Joel 答案背后的想法(即添加一个变量——我称之为“标志”——指示该函数是在外部调用还是作为递归的一部分)并将标志变量(我称之为internal_call,而乔尔称之为initial)移动到函数本身内。


此外,我不确定num 业务的全部内容。请注意:

  • 对于0 情况,rec_cou 返回 0。
  • 对于number &gt; 0,将num设置为rec_cou(number-1)返回的值,然后返回1+num

例如rec_cou(1)的情况,num设置为rec_cou(0),即0,则返回0 + 1,即1。同样,对于rec_cou(2),返回的值比rec_cou(1)的值大一,所以返回2

简而言之,对于每个自然数,rec_cou(number) 返回输入数的值。目前尚不清楚您要实现什么,但您所拥有的是一个身份功能,这似乎不太可能是您想要的。

【讨论】:

  • 非常好,我喜欢这个。将新参数集添加为 False。我想要这个,所有的数字都被打印出来,这就是我添加打印的原因。我所做的真的没有必要,创建一个变量 num 然后返回 num + 1,最好不要使用任何变量,例如 joel 示例。
猜你喜欢
  • 2022-01-01
  • 2012-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-28
  • 2020-05-20
  • 2020-01-05
相关资源
最近更新 更多