Steven D'Aprano steve at REMOVE-THIS-cybersource.com.au
Tue Dec 2 12:06:08 CET 2008

On Mon, 01 Dec 2008 19:06:24 -0600, Robert Kern wrote:
> As Neal has observed, there is a performance hit for creating functions
> inside of another function. 

True, but it's not a big hit, and I believe it is constant time 
regardless of the size of the function. The inner function has been 
(mostly) pre-compiled into bits, and assembling the bits is quite fast.

On my desktop, I measure the cost of assembling the inner function to be 
around the same as two function lookups and calls. 

>>> def outer():
...     def inner():
...             return None
...     return inner()
>>> def ginner():
...     return None
>>> def gouter():
...     return ginner()
>>> from timeit import Timer
>>> t1 = Timer('outer()', 'from __main__ import outer')
>>> t2 = Timer('gouter()', 'from __main__ import gouter, ginner')
>>> t1.repeat()
[1.782930850982666, 0.96469783782958984, 0.96496009826660156]
>>> t2.repeat()
[1.362678050994873, 0.77759003639221191, 0.58583498001098633]

Not very expensive.

> Every time you go through the outer
> function, you are creating new function objects for all of the inner
> functions. That's how you can get lexical scoping. It is not equivalent
> to defining the functions all at the top-level, where all of the
> function objects are created at once. The compiler can't optimize that
> overhead away because the overhead arises from implementing a real
> feature.

But the time it takes to parse the function and compile it to code is 
optimized away.

>>> outer.func_code.co_consts
(None, <code object inner at 0xb7e80650, file "<stdin>", line 2>)

Which makes me wonder, is there anything we can do with that code object 
from Python code? I can disassemble it:

>>> import dis
>>> dis.dis(outer.func_code.co_consts[1])
  3           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE

Anything else?


More information about the Python-list mailing list