【问题标题】:Intercepting return拦截返回
【发布时间】:2013-02-03 06:46:13
【问题描述】:

在 python 中有没有办法拦截(和更改)已编译函数的返回值?

基本思路是:我有一个函数

def a (n, acc):
    if n == 0: return acc
    return a (n - 1, acc + n)

我想修补它,使其行为如下:

def a (n, acc):
    if n == 0: return acc
    return lambda: a (n - 1, acc + n)

是否可以写一个函数f,比如f (a)产生一个函数,就像第二个代码sn-p一样?

我可以通过inspect给函数打补丁,如果python可以找到它的源,然后返回新编译的打补丁的函数,但这并没有多大帮助。

【问题讨论】:

    标签: python python-3.x


    【解决方案1】:

    如果我正确理解你想要什么,理论上是不可能的;您似乎正在描述的转换是一种会对等效函数产生不同影响的转换,具体取决于可能不会以编译形式保留的源代码的表面细节。例如,考虑给定函数的以下两个版本:

    def a (n, acc):
        print('called a(%d,%d)' % (n, acc))
        if n == 0: return acc
        return a (n - 1, acc + n)
    
    def a (n, acc):
        print('called a(%d,%d)' % (n, acc))
        if n == 0: return acc
        ret = a (n - 1, acc + n)
        return ret
    

    显然它们在功能上是相同的。在源代码中,唯一的区别是前者直接在某个表达式上使用return,而后者将该表达式的结果保存到局部变量中,然后在该变量上使用return。编译后的形式,完全没有区别。

    现在考虑“修补”版本:

    def a (n, acc):
        print('called a(%d,%d)' % (n, acc))
        if n == 0: return acc
        return lambda: a (n - 1, acc + n)
    
    def a (n, acc):
        print('called a(%d,%d)' % (n, acc))
        if n == 0: return acc
        ret = a (n - 1, acc + n)
        return lambda: ret
    

    显然这些是非常不同的:例如,如果n3acc 是0,那么前者打印called a(3,0) 并返回一个打印called a(2,3) 的函数并返回一个打印@ 的函数987654330@ 并返回一个打印called a(0,6) 并返回6 的函数,而后者打印called a(3,0)called a(2,3)called a(1,5)called a(0,6) 并返回一个函数,该函数返回一个函数,该函数返回一个返回的函数6.

    更广泛的区别在于,第一个“修补”函数在每次调用新返回值时执行一个计算步骤,而第二个“修补”版本在初始调用期间执行所有计算步骤,并且简单为了娱乐,安排了一系列后续的通话。只要有副作用(例如打印消息,或者例如递归太深以至于溢出堆栈),这种差异就会很重要。 调用者是否引入副作用也很重要:请注意,这些函数只会递归,直到其他一些代码重新定义a,此时版本之间存在差异计划继续重新调用a 和已经完成所有调用的版本。

    由于您无法区分两个“未修补”版本,因此您显然无法生成转换所暗示的不同“已修补”版本。

    【讨论】:

    • 你可以很好地区分两个未打补丁的版本。第一个以CALL_FUNCTION RETURN_VALUE 结尾,后一个以CALL_FUNCTION STORE_FAST LOAD_FAST RETURN_VALUE 结尾。
    • @Hyperboreus:不允许 Python 解释器优化它吗?
    • 我不确定是否可以优化创建局部变量。如果他们被允许这样做,我现在不会。我的没有。我的最后一条评论来自反汇编。
    • 上下文中的重要区别在于,您的一个示例是尾递归的,而另一个示例则不是,因为在递归调用之后仍有赋值。
    • @Hyperboreus:源文本确实如此,但编译后的形式是否必然如此? (顺便说一下,尾递归在 Python 中并不特殊,因为不允许实现进行尾调用优化。因此,当两个 Python 函数是否则等价。)
    【解决方案2】:

    感谢您的意见。我没有看到明显的:

    def inject (f):
        def result (*args, **kwargs):
            return lambda: f (*args, **kwargs)
        return result
    

    我接受了 davidchambers 的回答,因为他把我推向了正确的方向。

    【讨论】:

      【解决方案3】:

      卢克的回答很详细,但这可能仍然有帮助:

      >>> def f(*args, **kwargs):
      ...     return lambda: a(*args, **kwargs)
      ...
      >>> f(10, 0)()
      55
      

      【讨论】:

        猜你喜欢
        • 2014-05-05
        • 2019-03-25
        • 2019-12-28
        • 2018-09-08
        • 2020-03-14
        • 2018-06-27
        • 1970-01-01
        • 2017-06-19
        • 2019-10-07
        相关资源
        最近更新 更多