内联Python函数定义的性能

Mik*_*sky 10 python

对于比我更了解函数定义内部的人来说,这是一个普遍的问题.

一般来说,做这样的事情是否有性能权衡:

def my_function():
    def other_function():
        pass

    # do some stuff
    other_function()
Run Code Online (Sandbox Code Playgroud)

与:

def other_function():
    pass

def my_function():
    # do some stuff
    other_function()
Run Code Online (Sandbox Code Playgroud)

我之前已经看过开发人员内联函数,以保持一个小的,单一使用函数接近实际使用它的代码,但我总是想知道是否存在执行此类操作的内存(或计算)性能损失.

思考?

小智 7

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

与往常一样,"具有更好性能"的问题应始终通过分析代码来解决,也就是说它通常依赖于方法的签名和代码所做的事情.

例如,如果您将大型字典传递给单独的函数而不是引用本地框架,则最终会产生与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()')
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)

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")    
Run Code Online (Sandbox Code Playgroud)
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)
Run Code Online (Sandbox Code Playgroud)

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

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


mgi*_*son 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
Run Code Online (Sandbox Code Playgroud)

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

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