修改python中的函数而不调用它

时间:2014-04-03 02:17:29

标签: python function lambda

假设我在Python中有一个任意函数f,它接受参数。

def f(x): return 2*x

现在假设我想要一个函数,它接受一个函数并返回相同的函数,但是沿y轴翻转(如果它是图形的)。

显而易见的方法是

def reverse_fn(f): return lambda x, funct=f: funct(-x) 

但是,像这样的堆叠函数修改函数会在一段时间后最终破坏最大递归深度,因为结果只是一个调用另一个函数的函数,该函数一直调用更多函数。

在Python中创建函数修改函数的最佳方法是什么,可以反复使用而不需要过多的调用堆栈或嵌套函数?

3 个答案:

答案 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