内联Python函数

时间:2018-05-02 19:03:27

标签: python inline abstract-syntax-tree compiler-optimization

在C程序中,内联函数是一种相当直观的优化。如果内联函数的主体足够小,则最终将跳转保存到函数和创建堆栈帧,并将返回值存储在函数结果存储的任何位置,跳转到内联函数的“body”的末尾“而不是长跳到返回指针。

我有兴趣在Python中做同样的事情,将两个python函数转换为另一个有效的python函数,其中第一个被“内联”到第二个。对此的理想解决方案可能如下所示:

def g(x):
    return x ** 2

def f(y):
    return g(y + 3)

# ... Becomes ...

def inlined_f(y):
    return (y + 3) ** 2

显然,在一种像Python一样动态的语言中,自动执行这一点并非易事。我提出的最好的通用解决方案是使用dict捕获传递给函数的参数,将函数体包装在一次迭代for循环中,使用break跳转到函数的末尾,并将带有索引的参数的使用替换为参数字典。结果如下所示:

def inlined_f(y):
    _g = dict(x=y + 3)
    for ____ in [None]:
        _g['return'] = _g['x'] ** 2
        break
    _g_return = _g.get('return', None)
    del _g
    return _g_return

我不在乎它很难看,但我确实关心它不支持循环内的返回。 E.g:

def g(x):
    for i in range(x + 1):
        if i == x:
            return i ** 2
    print("Woops, you shouldn't get here")

def inlined_f(y):
    _g = dict(x=y + 3)
    for ____ in [None]:
        for _g['i'] in range(_g['x'] + 1):
            if _g['i'] == _g['x']:
                _g['return'] _g['i'] ** 2
                break  # <-- Doesn't exit function, just innermost loop
        print("Woops, you shouldn't get here")
    _g_return = _g.get('return', None)
    del _g
    return _g_return

我可以采用什么方法解决这个问题,避免需要使用break来“跳出”内联函数的主体?我也可以采用一种更好的通用方法,将一个Python函数内联到另一个函数中。

作为参考,我正在使用AST(抽象语法树)级别,因此使用解析的Python代码;很明显,除了字面值之外,我不知道在执行这种转换时会有什么价值或类型。生成的内联函数必须与原始函数的行为相同,并且必须支持调用函数时通常可用的所有功能。这在Python中是否可行?

编辑:我应该澄清,因为我使用了标签“优化”,我实际上并没有对性能提升感兴趣。生成的代码不需要更快,它只是不能调用内联函数,同时仍然表现相同。您可以假设这两个函数的源代码都可用作有效的Python。

2 个答案:

答案 0 :(得分:1)

return最接近的模拟可能会引发一个Exception,它可以从嵌套循环中弹出到内联函数的顶部&#34;。

class ReturnException(Exception):
    pass


g = dict(x=y + 3)
try:
    for j in some_loop:
        for _g['i'] in range(_g['x'] + 1):
            if _g['i'] == _g['x']:
                raise ReturnException(_g['i'] ** 2)
except ReturnException as e:
    _g['return'] = e.message
else:
    _g['return'] = None

我不知道有多少开销与异常相关联,或者这比简单地调用函数要快。

答案 1 :(得分:1)

源代码级别唯一合理的方法我看到,简化:

  • 将源解析为某个AST(或只使用the built-in AST)。
  • 复制代表函数体的子树。
  • 重命名子树中的变量,例如通过添加唯一的前缀。
  • 在呼叫站点,使用函数的新变量名称将所有传递的参数替换为赋值。
  • 取消通话并将其替换为您已准备好的功能机构。
  • 将AST序列化为源。

真正的问题是什么:

  • 发电机功能;只是不要内联它们。
  • 需要运行try部分的finally / finally下方的返回。可能很难正确重写; imho,最好留在内联。
  • 从需要运行__exit__部分的上下文管理器下返回。虽然并非不可能,但重写保留语义也很棘手;也可能最好不要内联。
  • 中间函数返回,尤其是来自多个循环结构的返回。您可能需要使用额外的变量替换它们并将其线程化到每个while语句的每个条件中,并且可能向for语句添加条件中断。同样,并非不可能,但最好不要内联。