Overhead of (was Reasoning behind) nested scope
Peter Otten
__peter__ at web.de
Wed Aug 4 02:44:10 EDT 2004
Nigel Rowe wrote:
> Peter Otten wrote:
>
>> Andy Baker wrote:
>>
>>> (On a side note is there any way to call a nested function from outside
>>> the parent? I was kind of expecting nested functions to be addressable
>>> through dot notation like methods are but I can see why that wouldn't be
>>> quite right. This might be a better question for the tutor list...)
>>
>> When you are nesting functions, you don't get one function sitting inside
>> another function. Instead, the function creation code of the "inner"
>> function is executed every time to the effect that you get a new inner
>> function every time you call the outer one:
>>
>>>>> def make():
>> ... def f(): return "shoobidoo"
>> ... return f
>> ...
>>>>> f1 = make()
>>>>> f2 = make()
>>>>> f1(), f2()
>> ('shoobidoo', 'shoobidoo')
>>>>> f1 is f2
>> False
>>
>> You wouldn't do that in cases like the above, when you get nothing in
>> return for the extra overhead, but somtimes nesting is useful - because
>> of the change in the scoping rules you now get readonly-closures:
>>
>>>>> def make(s):
>> ... def f(): return s
>> ... return f
>> ...
>>>>> f1 = make("now")
>>>>> f2 = make("what")
>>>>> f1(), f2()
>> ('now', 'what')
>>
>> Peter
>
> What work is actually done when the
> 'nested function creation code of the "inner" function'
> is executed?
>
> Given a quick test:-
> <code>
> def outer():
> def inner():
> pass
> return inner
>
> b1 = outer()
> b2 = outer()
>
> attrs=[a for a in dir(b1) if not a.startswith('_')]
> for a, a1, a2 in zip(attrs,
> [getattr(b1,a) for a in attrs],
> [getattr(b2,a) for a in attrs]):
> print a, a1 is a2
>
> </code>
> <result>
> func_closure True
> func_code True
> func_defaults True
> func_dict True
> func_doc True
> func_globals True
> func_name True
> </result>
>
> it appears that all the components of the inner function are the same,
> which just leaves the binding of the code object to 'inner'.
Not the binding, a new function object is created every time outer is run,
i. e. (faked, but I did it for real above with f1 and f2)
>>> b1 is b2
False
> Am I missing something, or is the overhead no worse than, say,
> foo=self.foo, where self.foo is a method?
If it were pure Python it would rather be something like
foo = Function(func_closure=..., func_code=..., ...)
Another perspective is to look at the byte code:
>>> import dis
>>> def outer():
... def inner(): pass
... return inner
...
>>> dis.dis(outer)
2 0 LOAD_CONST 1 (<code object inner at
0x4028eee0, file "<stdin>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (inner)
3 9 LOAD_FAST 0 (inner)
12 RETURN_VALUE
13 LOAD_CONST 0 (None)
16 RETURN_VALUE
>>> def inner():
... pass
...
>>> def outer2():
... return inner
...
>>> dis.dis(outer2)
2 0 LOAD_GLOBAL 0 (inner)
3 RETURN_VALUE
4 LOAD_CONST 0 (None)
7 RETURN_VALUE
In the example you get three extra instructions, LOAD_CONST, MAKE_FUNCTION
and STORE_FAST (and earn the slight benefit that inner is now a local
instead of a global). A constant code object is used, meaning compilation
takes place at most once when the module is loaded. There is a dedicated
op-code, so function creation should be faster than "normal" creation of a
class instance.
Now this is all nice and dandy, but how do the two contenders perform?
$ timeit.py -s"def inner(): pass" -s"def outer(): return inner" "outer()"
1000000 loops, best of 3: 0.469 usec per loop
$ timeit.py -s"def outer():" -s" def inner(): pass" -s" return inner"
"outer()"
1000000 loops, best of 3: 1.12 usec per loop
i. e. nesting the two functions roughly doubles execution time.
However, creation of an inner function often will only take a small fraction
of the total time spent in the outer function - in the end it's just a
matter of style.
I use inner functions only when they depend on the local context because I
think it nicely discriminates closures from helpers. I tend to omit
implicit parameters even for helper funtions, therefore nesting them would
gain me nothing.
Peter
More information about the Python-list
mailing list