内联 Python 函数
Inlining Python Function
在 C 程序中,内联函数是一种相当直观的优化。如果内联函数的主体足够小,您最终将跳转保存到函数和堆栈帧的创建,并将 return 值存储在函数结果将被存储的任何位置,跳转到函数的末尾内联函数的 "body" 而不是长跳转到 return 指针。
我有兴趣在 Python 中做同样的事情,将两个 python 函数转换为另一个有效的 python 函数,其中第一个 "inlined" 到第二个.一个理想的解决方案可能如下所示:
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
我不在乎它丑陋,但我确实在意它不支持循环内的 returns。例如:
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
到 "jump"?我也愿意接受一种整体上更好的通用方法,我可以将一个 Python 函数内联到另一个函数中。
作为参考,我在 AST(抽象语法树)级别工作,因此使用解析的 Python 代码;显然,除了字面值之外,我不知道在执行此转换时任何东西会有什么值或类型。生成的内联函数的行为必须与原始函数相同,并且必须支持调用函数时通常可用的所有功能。这在 Python 中甚至可能吗?
编辑:我应该澄清一下,因为我使用了标签 "optimization",我实际上对性能提升并不感兴趣。生成的代码不需要更快,它只是不能调用内联函数,同时仍然表现相同。您可以假设这两个函数的源代码都是有效的 Python.
可能最接近 return
的类似物是引发 Exception
,它可以从嵌套循环中弹出到 "inlined function" 的顶部。
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
我不知道有多少开销与异常相关,或者这是否比简单地调用函数更快。
唯一合理的方法在源级别我明白了,简化了:
- 将源代码解析成一些 AST(或者只使用 the built-in AST)。
- 复制代表函数主体的子树。
- 重命名子树中的变量,例如通过添加唯一前缀。
- 在调用站点,使用函数的新变量名称将所有传递的参数替换为赋值。
- 删除调用并将其替换为您准备的函数体。
- 将 AST 序列化回源代码。
真正的问题是什么:
- 生成器函数;只是不要内联它们。
- Returns 来自
try
/finally
需要 运行 的 finally
部分。可能很难正确重写;恕我直言,最好不加衬里。
- Returns 来自需要 运行
__exit__
部分的上下文管理器。虽然并非不可能,但重写保留语义也很棘手;也可能最好不要内联。
- 中函数 returns,尤其是在多个循环结构中。您可能需要用一个额外的变量替换它们,并将其线程化到每个
while
语句的每个条件中,并且可能向 for
语句添加一个条件中断。同样,并非不可能,但最好不要内联。
在 C 程序中,内联函数是一种相当直观的优化。如果内联函数的主体足够小,您最终将跳转保存到函数和堆栈帧的创建,并将 return 值存储在函数结果将被存储的任何位置,跳转到函数的末尾内联函数的 "body" 而不是长跳转到 return 指针。
我有兴趣在 Python 中做同样的事情,将两个 python 函数转换为另一个有效的 python 函数,其中第一个 "inlined" 到第二个.一个理想的解决方案可能如下所示:
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
我不在乎它丑陋,但我确实在意它不支持循环内的 returns。例如:
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
到 "jump"?我也愿意接受一种整体上更好的通用方法,我可以将一个 Python 函数内联到另一个函数中。
作为参考,我在 AST(抽象语法树)级别工作,因此使用解析的 Python 代码;显然,除了字面值之外,我不知道在执行此转换时任何东西会有什么值或类型。生成的内联函数的行为必须与原始函数相同,并且必须支持调用函数时通常可用的所有功能。这在 Python 中甚至可能吗?
编辑:我应该澄清一下,因为我使用了标签 "optimization",我实际上对性能提升并不感兴趣。生成的代码不需要更快,它只是不能调用内联函数,同时仍然表现相同。您可以假设这两个函数的源代码都是有效的 Python.
可能最接近 return
的类似物是引发 Exception
,它可以从嵌套循环中弹出到 "inlined function" 的顶部。
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
我不知道有多少开销与异常相关,或者这是否比简单地调用函数更快。
唯一合理的方法在源级别我明白了,简化了:
- 将源代码解析成一些 AST(或者只使用 the built-in AST)。
- 复制代表函数主体的子树。
- 重命名子树中的变量,例如通过添加唯一前缀。
- 在调用站点,使用函数的新变量名称将所有传递的参数替换为赋值。
- 删除调用并将其替换为您准备的函数体。
- 将 AST 序列化回源代码。
真正的问题是什么:
- 生成器函数;只是不要内联它们。
- Returns 来自
try
/finally
需要 运行 的finally
部分。可能很难正确重写;恕我直言,最好不加衬里。 - Returns 来自需要 运行
__exit__
部分的上下文管理器。虽然并非不可能,但重写保留语义也很棘手;也可能最好不要内联。 - 中函数 returns,尤其是在多个循环结构中。您可能需要用一个额外的变量替换它们,并将其线程化到每个
while
语句的每个条件中,并且可能向for
语句添加一个条件中断。同样,并非不可能,但最好不要内联。