假设我在Python中有一个任意函数f,它接受参数。
def f(x): return 2*x
现在假设我想要一个函数,它接受一个函数并返回相同的函数,但是沿y轴翻转(如果它是图形的)。
显而易见的方法是
def reverse_fn(f): return lambda x, funct=f: funct(-x)
但是,像这样的堆叠函数修改函数会在一段时间后最终破坏最大递归深度,因为结果只是一个调用另一个函数的函数,该函数一直调用更多函数。
在Python中创建函数修改函数的最佳方法是什么,可以反复使用而不需要过多的调用堆栈或嵌套函数?
答案 0 :(得分:2)
一种方法是编辑函数的字节码。这是一种非常先进的技术,也非常脆弱。所以,不要将它用于生产代码!
也就是说,有一个模块可以实现您想要的编辑类型。它被称为bytecodehacks
,于2000年4月1日首次发布(是的,这是一个愚人节的笑话,但它是一个完全功能性的笑话)。稍后的版本(从2005年开始)在我安装Python 2.7.6时运行良好;抓住它from CVS并像往常一样运行setup.py
。 (不要使用April2000版本;它不适用于较新的Pythons)。
bytecodehacks
基本上实现了许多实用程序例程,可以编辑代码段的字节码(函数,模块,甚至函数中的单个块)。例如,您可以使用它来实现宏。出于修改函数的目的,inline
工具可能是最有用的。
以下是使用reverse_fn
实现bytecodehacks
的方法:
from bytecodehacks.inline import inline
def reverse_fn(f):
def g(x):
# Note that we use a global name here, not `f`.
return _f(-x)
return inline(g, _f=f)
这就是全部! inline
负责将函数f
“内联”到g
的主体中。实际上,如果f(x)
为return 2*x
,则reverse_fn(f)
的返回值将是等效于return 2*(-x)
的函数(其中不会有任何函数调用)。
现在,bytecodehacks
的一个限制是变量重命名(extend_and_rename
中的inline.py
)有点愚蠢。因此,如果您连续1000次应用reverse_fn
,那么当局部变量名称将开始爆炸时,您将会出现大幅减速。我不确定如何解决这个问题,但如果你这样做,它将大大提高重复内联函数的性能。
答案 1 :(得分:1)
使用sys.setrecursionlimit()
可以增加1000的默认递归限制,但即使1000也是非常深的递归,如果您的包装器往往是您在示例中显示的这种微不足道的更改,则会产生严重的性能损失
如果您尝试从简单原语中逐步构建复杂函数,那么您可以做的是将复合函数组合为Python源文本并将它们传递给eval()
以获取可调用函数。这种方法具有显着的优点,即从1000个基元构建的函数不会产生1000个函数调用和执行时返回的成本。
请注意eval()
应谨慎使用;不要eval()
不受信任的来源。
eval()
每个创建的功能都相当昂贵,而且在不知道你想要做什么的情况下,很难提出建议。你也可以简单地编写一个程序来生成一个大的.py文件,其中包含你想要的复合函数。
答案 2 :(得分:0)
我认为如果不使用蹦床,任何不支持Tail Call Optimization的语言都不能实现这一点。另一种选择是提取所讨论函数的AST,并生成一个根本不调用原始函数的“全新”函数,但实现它并非易事,需要对Python的一些内部部分有很好的理解。
另一方面,蹦床很容易实现,但缺点是你的函数不再是简单的Python函数 - 每次需要进行递归调用时,它们会返回该调用,例如,形式中的元组(some_fn, args, kwargs)
(虽然正常的返回值将包含在1元组中),然后蹦床将为您进行调用,以便堆栈不会增长。
def rec(fn, *args, **kwargs):
return (fn, args, kwargs)
def value(val):
return (val,)
def tailrec(fn, *args, **kwargs):
while True:
ret = fn(*args, **kwargs)
if ret is None:
return None
elif len(ret) == 1:
return ret[0]
else:
fn, args, kwargs = ret # no kwargs supported if using tuples
def greet_a_lot(n):
if n > 0:
print "hello: " + str(n)
return rec(greet_a_lot, n - 1)
else:
return value("done")
print tailrec(greet_a_lot, 10000)
<强>输出:强>
hello: 100000
hello: 99999
...
hello: 3
hello: 2
hello: 1
done