在Python中嵌套函数时是否存在开销?

Xåp*_* - 59 python nested function

在Python中,如果我在父函数中有子函数,那么每次调用父函数时子函数是否"初始化"(创建)?将函数嵌套在另一个函数中是否存在任何开销?

Ray*_*ger 44

代码对象是预编译的,因此该部分没有开销.函数对象在每次调用时构建 - 它将函数名称绑定到代码对象,记录默认变量等.

执行摘要:这不是免费的.

>>> from dis import dis
>>> def foo():
        def bar():
                pass
        return bar

>>> dis(foo)
  2           0 LOAD_CONST               1 (<code object bar at 0x1017e2b30, file "<pyshell#5>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               0 (bar)

  4           9 LOAD_FAST                0 (bar)
             12 RETURN_VALUE 
Run Code Online (Sandbox Code Playgroud)


Dae*_*yth 40

是的,每次都会创建一个新对象.它可能不是问题,除非你有一个紧凑的循环.分析将告诉您它是否有问题.

In [80]: def foo():
   ....:     def bar():
   ....:         pass
   ....:     return bar
   ....: 

In [81]: id(foo())
Out[81]: 29654024

In [82]: id(foo())
Out[82]: 29651384
Run Code Online (Sandbox Code Playgroud)

  • 要清楚,每次都会创建一个新的函数对象.底层代码对​​象被重用.因此,无论内部函数的长度如何,开销都是恒定的. (37认同)
  • FWIW,如果函数被装饰,只要重新创建函数对象,也会调用装饰器. (3认同)

小智 14

有一个影响,但在大多数情况下它是如此之小,你不应该担心它 - 大多数非平凡的应用程序可能已经有性能瓶颈,其影响比这个大几个数量级.担心代码的可读性和可重用性.

这里有一些代码比较每次通过循环重新定义函数的性能,而不是重用预定义的函数.

import gc
from datetime import datetime

class StopWatch:
     def __init__(self, name):
         self.name = name

     def __enter__(self):
         gc.collect()
         self.start = datetime.now()

     def __exit__(self, type, value, traceback):
         elapsed = datetime.now()-self.start
         print '** Test "%s" took %s **' % (self.name, elapsed)

def foo():
     def bar():
          pass
     return bar

def bar2():
    pass

def foo2():
    return bar2

num_iterations = 1000000

with StopWatch('FunctionDefinedEachTime') as sw:
    result_foo = [foo() for i in range(num_iterations)]

with StopWatch('FunctionDefinedOnce') as sw:
    result_foo2 = [foo2() for i in range(num_iterations)]
Run Code Online (Sandbox Code Playgroud)

当我在运行OS X Lion的Macbook Air上运行Python 2.7时,我得到:

** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **
Run Code Online (Sandbox Code Playgroud)


Ben*_*man 6

我对此也很好奇,所以我决定弄清楚这会产生多少开销。TL;DR,答案并不多。

Python 3.5.2 (default, Nov 23 2017, 16:37:01) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from timeit import timeit
>>> def subfunc():
...     pass
... 
>>> def no_inner():
...     return subfunc()
... 
>>> def with_inner():
...     def s():
...         pass
...     return s()
... 
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__     import no_inner', number=1)
0.22971350199986773
>>> timeit('[with_inner() for _ in range(1000000)]', setup='from __main__ import with_inner', number=1)
0.2847519510000893
Run Code Online (Sandbox Code Playgroud)

我的直觉是查看百分比(with_inner 慢 24%),但在这种情况下,这个数字具有误导性,因为我们实际上永远不会从外部函数返回内部函数的值,尤其是对于不返回内部函数的值实际上做任何事情。
犯了那个错误后,我决定将它与其他常见的东西进行比较,看看这什么时候重要,什么时候不重要:

    >>> def no_inner():
    ...     a = {}
    ...     return subfunc()
    ... 
    >>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
    0.3099582109998664
Run Code Online (Sandbox Code Playgroud)

看看这个,我们可以看到它比创建一个空的字典(快速方法)花费的时间更少,所以如果你正在做任何重要的事情,这可能根本无关紧要。