内联Python函数定义的性能

时间:2015-01-06 18:52:36

标签: python

对于比我更了解函数定义内部的人的一般性问题。

一般来说,做这样的事情是否会影响绩效:

def my_function():
    def other_function():
        pass

    # do some stuff
    other_function()

对战:

def other_function():
    pass

def my_function():
    # do some stuff
    other_function()

我之前已经看过开发人员内联函数,以保持一个小的,单次使用的函数接近实际使用它的代码,但我总是想知道是否有一个内存(或计算)性能损失做这样的事情

思想?

2 个答案:

答案 0 :(得分:7)

将更大的函数拆分为更易读,更小的函数是编写Pythonic代码的一部分 - 显而易见的是你要完成的事情,更小的函数更容易阅读,检查错误,维护和重用。

一如既往,"具有更好的性能"问题应始终由profiling the code解决,也就是说它通常依赖于方法的签名和代码的作用。

e.g。如果您将大型字典传递给单独的函数而不是引用本地框架,那么与从另一个函数调用void函数相比,您将获得不同的性能特征。

例如,这里有一些微不足道的行为:

import profile
import dis

def callee():
    for x in range(10000):
        x += x
    print("let's have some tea now")

def caller():
    callee()


profile.run('caller()')

let's have some tea now
         26 function calls in 0.002 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 :0(decode)
        2    0.000    0.000    0.000    0.000 :0(getpid)
        2    0.000    0.000    0.000    0.000 :0(isinstance)
        1    0.000    0.000    0.000    0.000 :0(range)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
        2    0.000    0.000    0.000    0.000 :0(time)
        2    0.000    0.000    0.000    0.000 :0(utf_8_decode)
        2    0.000    0.000    0.000    0.000 :0(write)
        1    0.002    0.002    0.002    0.002 <ipython-input-3-98c87a49b247>:4(callee)
        1    0.000    0.000    0.002    0.002 <ipython-input-3-98c87a49b247>:9(caller)
        1    0.000    0.000    0.002    0.002 <string>:1(<module>)
        2    0.000    0.000    0.000    0.000 iostream.py:196(write)
        2    0.000    0.000    0.000    0.000 iostream.py:86(_is_master_process)
        2    0.000    0.000    0.000    0.000 iostream.py:95(_check_mp_mode)
        1    0.000    0.000    0.002    0.002 profile:0(caller())
        0    0.000             0.000          profile:0(profiler)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)

VS

import profile
import dis

def all_in_one():
    def passer():
        pass
    passer()
    for x in range(10000):
        x += x
    print("let's have some tea now")    

let's have some tea now
         26 function calls in 0.002 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        2    0.000    0.000    0.000    0.000 :0(decode)
        2    0.000    0.000    0.000    0.000 :0(getpid)
        2    0.000    0.000    0.000    0.000 :0(isinstance)
        1    0.000    0.000    0.000    0.000 :0(range)
        1    0.000    0.000    0.000    0.000 :0(setprofile)
        2    0.000    0.000    0.000    0.000 :0(time)
        2    0.000    0.000    0.000    0.000 :0(utf_8_decode)
        2    0.000    0.000    0.000    0.000 :0(write)
        1    0.002    0.002    0.002    0.002 <ipython-input-3-98c87a49b247>:4(callee)
        1    0.000    0.000    0.002    0.002 <ipython-input-3-98c87a49b247>:9(caller)
        1    0.000    0.000    0.002    0.002 <string>:1(<module>)
        2    0.000    0.000    0.000    0.000 iostream.py:196(write)
        2    0.000    0.000    0.000    0.000 iostream.py:86(_is_master_process)
        2    0.000    0.000    0.000    0.000 iostream.py:95(_check_mp_mode)
        1    0.000    0.000    0.002    0.002 profile:0(caller())
        0    0.000             0.000          profile:0(profiler)
        2    0.000    0.000    0.000    0.000 utf_8.py:15(decode)

两者使用相同数量的函数调用,并且没有性能差异,这支持了我在特定情况下测试真正重要的说法。

您可以看到disassembly模块的导入未使用。这是另一个有用的模块,可以让您查看代码正在执行的操作(尝试dis.dis(my_function))。我发布了我生成的测试代码的个人资料,但它只会向您显示与解决问题或了解代码中实际发生的内容无关的更多详细信息。

答案 1 :(得分:5)

在我的mac上使用timeit似乎更倾向于在模块级别(稍微)定义函数,显然结果可能因计算机而异...:

>>> import timeit
>>> def fun1():
...   def foo():
...     pass
...   foo()
... 
>>> def bar():
...   pass
... 
>>> def fun2():
...   bar()
... 
>>> timeit.timeit('fun1()', 'from __main__ import fun1')
0.2706329822540283
>>> timeit.timeit('fun2()', 'from __main__ import fun2')
0.23086285591125488

请注意,这种差异很小(~10%),所以它确实不会在程序的运行时间产生重大影响,除非这是一个非常紧凑的循环。

在另一个函数中定义函数的最常见原因是在闭包中拾取out函数的局部变量。如果您不需要关闭,那么您应该选择最容易阅读的变体。 (我的偏好几乎总是将函数放在模块级别。)