1

Python 函数调用相对昂贵。 但是我不断遇到我希望能够以不同方式调用函数的情况,最简单的方法似乎是使用不同的调用在函数周围创建一个轻量级包装器。

是否有比调用函数的方式更pythonic和/或更有效的方式来启用更多功能?


对于一个完全人为的、过于简单的例子来说明我在问什么:

from math import sqrt
from collections import namedtuple

Point = namedtuple('Point', 'x y')

def distFunc(x1, y1, x2, y2):
    return sqrt((x1-x2)**2 + (y1-y2)**2)

def pointDistFunc(p1, p2):
    return distFunc(p1.x, p1.y, p2.x, p2.y)

有没有更好的方法来编写 pointDistFunc?

事实上,这一次:

p1 = Point(1, 1)
p2 = Point(100, 100)

if __name__ == '__main__':
    import timeit
    print(timeit.timeit("distFunc(1, 1, 100, 100)", setup="from __main__ import distFunc"))
    print(timeit.timeit('pointDistFunc(p1, p2)', setup= 'from __main__ import pointDistFunc, p1, p2'))

给出:

0.392938508373
0.977704155415

所以开销似乎很明显。

4

1 回答 1

2

我认为,总的来说,最好的办法是写最清晰的代码,不要过分担心效率。我认为在这种情况下,我会按照您已经做过的方式对其进行编码,而不必担心。

但是如果你知道一些代码会被大量调用,并且你希望它尽可能快,那么你也许可以通过重写来加快速度。在您的简单示例中,您可以通过重写包装器来进行计算来提高速度:

def pointDistFunc(p1, p2):
    return sqrt((p1.x-p2.x)**2 + (p1.y-p2.y)**2)

理想情况下,您应该在某处进行一些单元测试来检查

pointDistFunc(p1, p2) == distFunc(p1.x, p1.y, p2.x, p2.y)

这样,如果您最终更改distFunc()但忘记也更改pointDistFunc()测试将失败并且您会被提醒。

您提到的那条指南并不是要阻止您编写包装器;更多的是建议如何重写涉及列表之类的热点:

def gen_point_dist_from_lst(lst, p2):
    return (sqrt((p1.x-p2.x)**2 + (p1.y-p2.y)**2) for p1 in lst)

如果列表有 1000 个点,那么与直接的生成器表达式相比,上面的函数调用节省了 2000 个函数调用

(pointDistFunc(p1, p2) for p1 in lst)

关键是在你尝试这些技巧之前先真正遇到问题。如果您的程序已经足够快地运行,也许您不需要优化任何东西。如果需要您的代码更快,您可以尝试这些技巧。

PS 如果你可以使用 PyPy 来做你正在做的事情,它应该可以消除函数调用的开销。PyPy 有一个即时编译器,可以为您优化程序中的热点。

http://speed.pypy.org/

于 2013-03-06T23:20:54.453 回答