有没有办法检查 python 中函数是否递归?
Is there a way to check if function is recursive in python?
我想为练习编写一个测试函数,以确保函数被正确实现。
所以我想知道,给定一个函数"foo",有没有办法检查它是否递归实现?
如果它封装了一个递归函数并使用它,它也算在内。例如:
def foo(n):
def inner(n):
#more code
inner(n-1)
return inner(n)
这也应该被认为是递归的。
请注意,我想使用 external 测试函数来执行此检查。不改变函数的原始代码。
解决方案:
from bdb import Bdb
import sys
class RecursionDetected(Exception):
pass
class RecursionDetector(Bdb):
def do_clear(self, arg):
pass
def __init__(self, *args):
Bdb.__init__(self, *args)
self.stack = set()
def user_call(self, frame, argument_list):
code = frame.f_code
if code in self.stack:
raise RecursionDetected
self.stack.add(code)
def user_return(self, frame, return_value):
self.stack.remove(frame.f_code)
def test_recursion(func):
detector = RecursionDetector()
detector.set_trace()
try:
func()
except RecursionDetected:
return True
else:
return False
finally:
sys.settrace(None)
示例usage/tests:
def factorial_recursive(x):
def inner(n):
if n == 0:
return 1
return n * factorial_recursive(n - 1)
return inner(x)
def factorial_iterative(n):
product = 1
for i in xrange(1, n+1):
product *= i
return product
assert test_recursion(lambda: factorial_recursive(5))
assert not test_recursion(lambda: factorial_iterative(5))
assert not test_recursion(lambda: map(factorial_iterative, range(5)))
assert factorial_iterative(5) == factorial_recursive(5) == 120
本质上 test_recursion
接受一个不带参数的可调用对象,调用它,并且 returns True
如果在执行该可调用对象的任何时候相同的代码在堆栈中出现两次, False
否则。我认为这可能不是 OP 想要的。它可以很容易地修改以测试,比如说,相同的代码是否在特定时刻在堆栈中出现 10 次。
我还没有亲自验证 Alex 的答案是否有效(虽然我认为它有效,并且比我将要提出的要好得多),但如果你想要比这更简单(和更小)的东西,您可以简单地使用 sys.getrecursionlimit()
手动将其出错,然后在函数中检查它。例如,这是我自己写的递归验证:
import sys
def is_recursive(function, *args):
try:
# Calls the function with arguments
function(sys.getrecursionlimit()+1, *args)
# Catches RecursionError instances (means function is recursive)
except RecursionError:
return True
# Catches everything else (may not mean function isn't recursive,
# but it means we probably have a bug somewhere else in the code)
except:
return False
# Return False if it didn't error out (means function isn't recursive)
return False
虽然它可能不太优雅(并且在某些情况下更容易出错),但它 远 比 Alex 的代码小,并且在大多数情况下工作得相当好。这里的主要缺点是,使用这种方法,您让计算机处理函数经历的每次递归,直到达到递归限制。我建议在使用此代码时暂时更改 sys.setrecursionlimit()
的递归限制,以最大限度地减少处理递归所花费的时间,如下所示:
sys.setrecursionlimit(10)
if is_recursive(my_func, ...):
# do stuff
else:
# do other stuff
sys.setrecursionlimit(1000) # 1000 is the default recursion limit
from inspect import stack
already_called_recursively = False
def test():
global already_called_recursively
function_name = stack()[1].function
if not already_called_recursively:
already_called_recursively = True
print(test()) # One recursive call, leads to Recursion Detected!
if function_name == test.__name__:
return "Recursion detected!"
else:
return "Called from {}".format(function_name)
print(test()) # Not Recursion, "father" name: "<module>"
def xyz():
print(test()) # Not Recursion, "father" name: "xyz"
xyz()
输出为
Recursion detected!
Called from <module>
Called from xyz
我使用全局变量 already_called_recursively
来确保我只调用它一次,如您所见,在递归时它显示“检测到递归”,因为“父亲”名称与当前函数,这意味着我从同一个函数调用它,也就是递归。
其他打印的是模块级的调用,里面的调用xyz
.
希望对您有所帮助:D
我想为练习编写一个测试函数,以确保函数被正确实现。
所以我想知道,给定一个函数"foo",有没有办法检查它是否递归实现?
如果它封装了一个递归函数并使用它,它也算在内。例如:
def foo(n):
def inner(n):
#more code
inner(n-1)
return inner(n)
这也应该被认为是递归的。
请注意,我想使用 external 测试函数来执行此检查。不改变函数的原始代码。
解决方案:
from bdb import Bdb
import sys
class RecursionDetected(Exception):
pass
class RecursionDetector(Bdb):
def do_clear(self, arg):
pass
def __init__(self, *args):
Bdb.__init__(self, *args)
self.stack = set()
def user_call(self, frame, argument_list):
code = frame.f_code
if code in self.stack:
raise RecursionDetected
self.stack.add(code)
def user_return(self, frame, return_value):
self.stack.remove(frame.f_code)
def test_recursion(func):
detector = RecursionDetector()
detector.set_trace()
try:
func()
except RecursionDetected:
return True
else:
return False
finally:
sys.settrace(None)
示例usage/tests:
def factorial_recursive(x):
def inner(n):
if n == 0:
return 1
return n * factorial_recursive(n - 1)
return inner(x)
def factorial_iterative(n):
product = 1
for i in xrange(1, n+1):
product *= i
return product
assert test_recursion(lambda: factorial_recursive(5))
assert not test_recursion(lambda: factorial_iterative(5))
assert not test_recursion(lambda: map(factorial_iterative, range(5)))
assert factorial_iterative(5) == factorial_recursive(5) == 120
本质上 test_recursion
接受一个不带参数的可调用对象,调用它,并且 returns True
如果在执行该可调用对象的任何时候相同的代码在堆栈中出现两次, False
否则。我认为这可能不是 OP 想要的。它可以很容易地修改以测试,比如说,相同的代码是否在特定时刻在堆栈中出现 10 次。
我还没有亲自验证 Alex 的答案是否有效(虽然我认为它有效,并且比我将要提出的要好得多),但如果你想要比这更简单(和更小)的东西,您可以简单地使用 sys.getrecursionlimit()
手动将其出错,然后在函数中检查它。例如,这是我自己写的递归验证:
import sys
def is_recursive(function, *args):
try:
# Calls the function with arguments
function(sys.getrecursionlimit()+1, *args)
# Catches RecursionError instances (means function is recursive)
except RecursionError:
return True
# Catches everything else (may not mean function isn't recursive,
# but it means we probably have a bug somewhere else in the code)
except:
return False
# Return False if it didn't error out (means function isn't recursive)
return False
虽然它可能不太优雅(并且在某些情况下更容易出错),但它 远 比 Alex 的代码小,并且在大多数情况下工作得相当好。这里的主要缺点是,使用这种方法,您让计算机处理函数经历的每次递归,直到达到递归限制。我建议在使用此代码时暂时更改 sys.setrecursionlimit()
的递归限制,以最大限度地减少处理递归所花费的时间,如下所示:
sys.setrecursionlimit(10)
if is_recursive(my_func, ...):
# do stuff
else:
# do other stuff
sys.setrecursionlimit(1000) # 1000 is the default recursion limit
from inspect import stack
already_called_recursively = False
def test():
global already_called_recursively
function_name = stack()[1].function
if not already_called_recursively:
already_called_recursively = True
print(test()) # One recursive call, leads to Recursion Detected!
if function_name == test.__name__:
return "Recursion detected!"
else:
return "Called from {}".format(function_name)
print(test()) # Not Recursion, "father" name: "<module>"
def xyz():
print(test()) # Not Recursion, "father" name: "xyz"
xyz()
输出为
Recursion detected!
Called from <module>
Called from xyz
我使用全局变量 already_called_recursively
来确保我只调用它一次,如您所见,在递归时它显示“检测到递归”,因为“父亲”名称与当前函数,这意味着我从同一个函数调用它,也就是递归。
其他打印的是模块级的调用,里面的调用xyz
.
希望对您有所帮助:D