【问题标题】:What is the performance overhead of nested functions? [duplicate]嵌套函数的性能开销是多少? [复制]
【发布时间】:2012-12-12 21:53:27
【问题描述】:

在 Python 中,可以像这样嵌套函数:

def do_something():
    def helper():
        ....
    ....

除非 Python 更巧妙地处理这种情况,否则每次使用 do_something 时都必须重新创建 helper。实际上这样做是否会对性能造成影响,而不是在主函数之外创建辅助函数,如果是,它有多好?

【问题讨论】:

    标签: python performance function


    【解决方案1】:

    是的,在 main 函数中声明一个助手比单独声明它们要慢:

    ### test_nested.py ###
    import timeit
    def foo():
        def bar():
            pass
        pass
    print(timeit.timeit("foo()", setup="from __main__ import foo"))
    
    ### test_flat.py ###
    import timeit
    def foo():
        pass
    def bar():
        pass
    print(timeit.timeit("foo()", setup="from __main__ import foo, bar"))
    
    
    ### Shell ###
    ✗ python3 ./test_flat.py
    0.42562198638916016
    ✗ python3 ./test_nested.py
    0.5836758613586426
    

    这是一个大约 30% 的减速。请记住,在这种简单的情况下,创建和调用函数就是解释器所做的全部工作。在任何实际使用中,差异都会小得多。

    【讨论】:

    • 确实,因为每次执行外部函数时都会重新创建内部嵌套函数对象;创建对象涉及调用函数构造函数并传入(已编译的)代码对象。
    • 我刚注意到我什至忘了打电话给bar,所以与现实生活相比,这次测试的影响比我预期的还要夸张。
    • 根据Raymond Hettinger's answer to "Is there an overhead when nesting functions in Python?",代码对象被重用,所以不管内部函数的长度(例如),唯一的开销来自函数对象的O(1)创建。所以嵌套函数不是免费的(我猜这就像添加一个赋值),但是当你的嵌套函数“太大”时你也不必担心:无论你的嵌套函数是微不足道的还是非平凡的,开销都是一样的。
    【解决方案2】:

    性能损失肯定存在。如果一个函数是在对另一个函数的调用中创建的,那么每次调用外部函数时都会真正创建函数对象。但这种惩罚很小,通常可以忽略不计。尤其是考虑到一个显而易见的事实:在大多数情况下,只有当它不能放在外面时,您才应该创建嵌套函数。

    您可能需要嵌套函数的原因是需要在嵌套函数内部访问外部函数的作用域变量。通常这将导致直接或间接地从外部函数返回内部函数对象(如在装饰器中),或者,可能会将内部函数作为回调传递给某处。嵌套函数访问的变量会一直存在,直到嵌套函数对象被销毁,并且对于嵌套函数的不同实例,它们会有所不同,因为每个实例看到的变量来自不同的作用域实例。

    在我看来,仅将创建空的内部函数所需的时间与使用放置在外部的相同函数进行比较几乎没有意义。性能差异纯粹源于代码行为的差异。所需的代码行为应该让您选择放置函数的位置。

    只是一个小插图:

    def outer(n):
        v1 = "abc%d" % n
        v2 = "def"
        def inner():
            print locals().keys()
            return v1
        v1 = "_" + v1
        return inner
    f1 = outer(1)
    f2 = outer(2)
    print f1()
    print f2()
    

    输出是:

    ['v1']
    _abc1
    ['v1']
    _abc2
    

    关键时刻:

    1. 内部函数的 locals() 仅包括它使用的外部函数 locals(v1,但不包括 v2)。

    2. 创建函数对象后更改 v1。但是,即使 v1 的类型是不可变的 (str),内部函数仍然可以看到更改。因此,内部函数看到的是外部函数局部变量的真正子集,而不仅仅是在函数对象创建时存储的引用。幸运的是,内部函数对象的存在并不能阻止 v1 以外的范围变量被破坏。如果我将 v2 值替换为在销毁时打印某些内容的对象,它会在外部函数退出时立即打印消息。

    3. inner() 的不同实例不共享一个外部作用域实例:v1 值不同。

    如果不使用嵌套函数,所有这些效果根本无法实现。这就是为什么应该使用嵌套函数的原因,实际上并没有性能损失:额外的行为需要额外的时间。如果您需要这种额外的行为,您应该使用嵌套函数。如果你不需要它,你不应该。

    【讨论】:

    • 我不同意你的说法,即这是唯一应该使用嵌套函数的情况。通常,我会在(唯一的)使用它的函数中放置一个辅助函数,因为a)不需要用它来混淆模块范围,b)因为这样可以更明显地看出辅助函数所属的位置。
    • 当然,也可能有例外。有时(很少)我也这样做只是为了隐藏一个函数(尽管通常在其名称前加上下划线对我来说就足够了)。但当我关心性能时却不是。
    猜你喜欢
    • 2021-09-09
    • 2017-09-27
    • 2012-03-07
    • 2018-04-04
    • 1970-01-01
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    相关资源
    最近更新 更多