In Python, if I have a child function within a parent function, is the child function "initialised" (created) every time the parent function is called? Is there any performance overhead associated with nesting a function within another?
6 Answers
The code object is pre-compiled so that part has no overhead. The function object gets built on every invocation -- it binds the function name to the code object, records default variables, etc.
Executive summary: It's not free.
>>> 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

- 216,523
- 63
- 388
- 485
Yes, a new object would be created each time. It's likely not an issue unless you have it in a tight loop. Profiling will tell you if it's a problem.
In [80]: def foo():
....: def bar():
....: pass
....: return bar
....:
In [81]: id(foo())
Out[81]: 29654024
In [82]: id(foo())
Out[82]: 29651384

- 574,206
- 118
- 941
- 841

- 35,856
- 13
- 85
- 124
-
48To be clear, a new function object is created each time. The underlying code object is reused. So, the overhead is constant regardless of the length of the inner function. – Raymond Hettinger Oct 20 '11 at 17:53
-
5FWIW, if the function is decorated, the decorator is called whenever the function object is recreated as well. – kindall Oct 20 '11 at 18:40
-
... though in many cases that just means you get two or three `O(1)` function object creations. Decorators that do heavy lifting on creation are rare, most just create a small object or a closure. – Oct 20 '11 at 18:51
-
The two ids are the same by mere chance. Python happens to use the same memory for the second `bar()` because the first one is immediately garbage collected. Try `a = foo(); b = foo()` and compare the ids (they'll be different). See http://stackoverflow.com/questions/2906177/what-is-the-difference-between-a-is-b-and-ida-idb-in-python/2906209#2906209 for a related explanation. – Sven Marnach Oct 28 '11 at 20:49
-
1@SvenMarnach: I'm aware of what you're trying to say, but the ids are not the same in my answer. (Also ipython holds the result of the call in a variable automatically, so they both would not have been gc'd anyway) – Daenyth Oct 29 '11 at 01:52
-
@Daenyth: Sorry, my bad. On my machine, they happened to be the same. (Note that IPython only caches the result of the whole expression, not of all intermdeiary expressions.) – Sven Marnach Oct 29 '11 at 09:30
There is an impact, but in most situations it is so small that you shouldn't worry about it - most non-trivial applications probably already have performance bottlenecks whose impacts are several orders of magnitude larger than this one. Worry instead about the readability and reusability of the code.
Here some code that compares the performance of redefining a function each time through a loop to reusing a predefined function instead.
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)]
When I run this in Python 2.7 on my Macbook Air running OS X Lion I get:
** Test "FunctionDefinedEachTime" took 0:00:01.138531 **
** Test "FunctionDefinedOnce" took 0:00:00.270347 **

- 311
- 2
- 3
I was curious about this too, so I decided to figure out how much overhead this incurred. TL;DR, the answer is not much.
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
My instinct was to look at percents (with_inner is 24% slower), but that number is misleading in this case, since we'll never actually just return the value of an inner function from an outer function, especially with functions that don't actually do anything.
After making that mistake, I decided to compare it to other common things, to see when this does and does not matter:
>>> def no_inner():
... a = {}
... return subfunc()
...
>>> timeit('[no_inner() for _ in range(1000000)]', setup='from __main__ import no_inner', number=1)
0.3099582109998664
Looking at this, we can see that it takes less time than creating an empty dict (the fast way), so if you're doing anything non-trivial, this probably does not matter at all.
The other answers are great and really answer the question well. I wanted to add that most inner functions can be avoided in python using for loops, generating functions, etc.
Consider the following Example:
def foo():
# I need to execute a function on two sets of arguments:
argSet1 = (1, 3, 5, 7)
argSet2 = (2, 4, 6, 8)
# A Function could be executed on each set of args
def bar(arg1, arg2, arg3, arg4):
return (arg1 + arg2 + arg3 + arg4)
total = 0
for argSet in [argSet1, argSet2]:
total += bar(*argSet)
print( total )
# Or a loop could be used on the argument sets
total = 0
for arg1, arg2, arg3, arg4 in [argSet1, argSet2]:
total += arg1 + arg2 + arg3 + arg4
print( total )
This example is a little goofy, but I hope you can see my point nonetheless. Inner functions are often not needed.

- 1,025
- 14
- 26
Yes. This enables closures, as well as function factories.
A closure causes the inner function to remember the state of its environment when called.
def generate_power(number):
# Define the inner function ...
def nth_power(power):
return number ** power
return nth_power
Example
>>> raise_two = generate_power(2)
>>> raise_three = generate_power(3)
>>> print(raise_two(3))
8
>>> print(raise_three(5))
243
"""

- 831
- 8
- 22