16

In Python, it is possible to nest functions like this:

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

Unless Python handles this case more smartly, helper has to be created anew every time do_something is used. Is there in fact a performance hit from doing this instead of creating the helper function outside of the main one and if yes, how great is it?

Nova
  • 2,623
  • 4
  • 26
  • 45

2 Answers2

15

Yes, declaring a helper inside the main function is slower that declaring them separately:

### 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

That's a slowdown of about 30%. Remember that in this trivial case creating and calling functions is all the interpreter does. In any real usage the difference will be much less.

Nova
  • 2,623
  • 4
  • 26
  • 45
  • 4
    Indeed, because the inner, nested function object is recreated every time the outer function is executed; creating the object involves calling the function constructor and passing in the (already compiled) code object. – Martijn Pieters Dec 27 '12 at 14:12
  • I just noticed that I forgot to even call `bar` so the impact is even more exaggerated in this test compared to real life than I intended. – Nova Dec 28 '12 at 13:37
  • 4
    According to [Raymond Hettinger's answer to "Is there an overhead when nesting functions in Python?"](http://stackoverflow.com/a/7839697/4958), the code object is reused, so irrespective of the length of the inner function (for example), the only overhead is from the O(1) creation of the function object. So the nested function is not free (it's like adding an assignment I guess), but you also don't need to worry when your nested function is "too large": the overhead is the same whether your nested function is trivial or nontrivial. – ShreevatsaR Nov 22 '16 at 18:21
  • `Nested functions can be converted to simple class.` – Bernard Agbemadon Oct 20 '22 at 16:03
  • 1
    Tested on python3.8.5 and python3.10.6 and the ~30% slower for nested version is still correct. – imkzh Mar 11 '23 at 10:39
7

The performance penalty definitely exists. In case a function is created inside a call to another function, the function object is really created every time the outer function is called. But that penalty is small and usually may be ignored. Especially taking into account the obvious fact: in most cases you should create a nested function only if it cannot be placed outside.

The reason why you may need to have a nested function is need to access the outer function's scope variables inside the nested function. Usually that will lead to directly or indirectly returning the inner function object from the outer function (like in decorators), or, maybe, to passing the inner function somewhere as a callback. The variables accessed by nested function will exist until the nested function object is destroyed, and they will be different for different instances of nested function since each one sees the variables from different scope instances.

To my mind, just comparing times required for creating an empty inner function to using the same function placed outside is almost pointless. The performance differences arise purely from differences in code behavior. The desired code behavior is what should make you select where to place your function.

Just a small illustration:

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()

The output is:

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

The key moments:

  1. Inner function's locals() include only the outer function locals it uses (v1, but not v2).

  2. v1 is changed after the function object is created. However, the changes are still visible to inner function, even though v1's type is immutable (str). So, what the inner function sees is a real subset of outer function's locals, not just references stored at the moment of function object creation. Fortunately, existence of inner function object does not prevent scope variables other than v1 from destruction. If I replace v2 value with an object that prints something when being destroyed, it prints the message immediately when outer function exits.

  3. Different instances of inner() do not share a single outer scope instance: v1 values differ.

All these effects simply can not be achieved without using a nested function. And that is why nested functions should be used, and in fact there is no performance penalty: extra behavior requires extra time. If you need that extra behavior, you should use nested functions. If you don't need it, you should not.

Ellioh
  • 5,162
  • 2
  • 20
  • 33
  • 6
    I disagree with your assertion that this is the only case where nested functions should be used. Often, I place a helper function inside the (only) one that uses it because there is a) no need to clutter the module scope with it and b) because that way it is more obvious where the helper belongs to. – Nova Dec 28 '12 at 13:32
  • Of course, there may be exceptions. Sometimes (very rarely) I also do so just to hide a function (though usually prefixing its name with an underscore is enough for me). But not when I care about performance. – Ellioh Dec 28 '12 at 16:03