Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

optimization

2 views
Skip to first unread message

Neal Becker

unread,
Dec 1, 2008, 12:41:24 PM12/1/08
to pytho...@python.org
I noticed in some profiling, that it seems that:

def Func ():
def something():
...

It appears that if Func is called many times, this nested func definition will cause significant overhead. Is this true? I guess I've become accustomed to decent compilers performing reasonable transformations and so have tended to write code for clarity.

sk...@pobox.com

unread,
Dec 1, 2008, 1:06:02 PM12/1/08
to Neal Becker, pytho...@python.org

Neal> I noticed in some profiling, that it seems that:

Neal> def Func ():
Neal> def something():
Neal> ...

Neal> It appears that if Func is called many times, this nested func
Neal> definition will cause significant overhead. Is this true? I
Neal> guess I've become accustomed to decent compilers performing
Neal> reasonable transformations and so have tended to write code for
Neal> clarity.

It could. OTOH, the code object which implements the something body is
stored as a local var (or a constant, can't remember off the top of my
head), so it's not compiled over and over again.

--
Skip Montanaro - sk...@pobox.com - http://smontanaro.dyndns.org/

Terry Reedy

unread,
Dec 1, 2008, 1:59:51 PM12/1/08
to pytho...@python.org
sk...@pobox.com wrote:
> Neal> I noticed in some profiling, that it seems that:
>
> Neal> def Func ():
> Neal> def something():
> Neal> ...
>
> Neal> It appears that if Func is called many times, this nested func
> Neal> definition will cause significant overhead. Is this true? I
> Neal> guess I've become accustomed to decent compilers performing
> Neal> reasonable transformations and so have tended to write code for
> Neal> clarity.
>
> It could. OTOH, the code object which implements the something body is
> stored as a local var (or a constant, can't remember off the top of my
> head), so it's not compiled over and over again.

Constant. With 3.0...

>>> def f():
def g():
pass


>>> import dis
>>> dis.dis(f)
2 0 LOAD_CONST 1 (<code object g at
0x0137D920, file "<pyshell#4>", line 2>)
3 MAKE_FUNCTION 0
6 STORE_FAST 0 (g)
9 LOAD_CONST 0 (None)
12 RETURN_VALUE

If the inner function is constant and does not directly access outer
function locals, and if every last tick of speed is a concern, then it
can be move out and given a name like _outer_helper.

I would go for clarity and correctness first, but I would also wonder
whether an inner function that is independent of its setting and
therefore movable might be turned into something of more general use and
usefully moved out for purposes other than just speed.

Terry Jan Reedy


Arnaud Delobelle

unread,
Dec 1, 2008, 5:08:05 PM12/1/08
to
Neal Becker <ndbe...@gmail.com> writes:

If something() can be defined outside Func(), how is it clearer to
define it inside?

--
Arnaud

Neal Becker

unread,
Dec 1, 2008, 6:00:15 PM12/1/08
to pytho...@python.org
Arnaud Delobelle wrote:

If it's only used inside.

Robert Kern

unread,
Dec 1, 2008, 7:11:16 PM12/1/08
to pytho...@python.org
Neal Becker wrote:
> If it's only used inside.

I, for one, find that significantly less clear. I only expect functions to be
defined inside of functions if they are going to use lexical scoping for some
reason. If I read your code, I'd probably waste a good five minutes trying to
figure out what part of the local scope you were using before I would conclude
that you just did it because you thought it looked better.

--
Robert Kern

"I have come to believe that the whole world is an enigma, a harmless enigma
that is made terrible by our own mad attempt to interpret it as though it had
an underlying truth."
-- Umberto Eco

bearoph...@lycos.com

unread,
Dec 1, 2008, 7:43:18 PM12/1/08
to
Robert Kern:

>I only expect functions to be defined inside of functions if they are going to use lexical scoping for some reason.<

There are other reasons to nest functions. One of them is to represent
logical nesting of some functionality.
So you will find some exceptions to your self-created rule :-)

Bye,
bearophile

Robert Kern

unread,
Dec 1, 2008, 8:06:24 PM12/1/08
to pytho...@python.org
bearoph...@lycos.com wrote:
> Robert Kern:
>> I only expect functions to be defined inside of functions if they are going to use lexical scoping for some reason.<
>
> There are other reasons to nest functions. One of them is to represent
> logical nesting of some functionality.

Is that any different than Neal's "if it's only used inside"?

> So you will find some exceptions to your self-created rule :-)

It's not a rule; it's just what I expect after many years of programming Python
and reading lots of Python code. Most people define functions at the top-level
regardless of whether they are used once or not. Defining a function inside of a
function is an oddity. Lexical scoping requires that you define a function
inside of a function, so that is always my first assumption about why the author
defined the function there. I only fall back to "the author just wanted to
organize things in a different way" when I fail to find the lexically-scoped
variables, or I see a comment explaining it. I'm pretty sure that lexical
scoping (and its poor-man implementation via keyword arguments before) is what
the def-in-a-def feature was for, not organization.

As Neal has observed, there is a performance hit for creating functions inside
of another function. 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.

Steven D'Aprano

unread,
Dec 2, 2008, 5:27:00 AM12/2/08
to
On Mon, 01 Dec 2008 18:11:16 -0600, Robert Kern wrote about nested
functions:

> I, for one, find that significantly less clear. I only expect functions
> to be defined inside of functions if they are going to use lexical
> scoping for some reason. If I read your code, I'd probably waste a good
> five minutes trying to figure out what part of the local scope you were
> using before I would conclude that you just did it because you thought
> it looked better.

Hah, I bet you aren't an ex-Pascal programmer :-)

Speaking as one, it took me a long time to teach myself not to bother
nesting functions for the purpose of avoiding scoping clashes. I'd write
something like this:

def parrot():
def colour():
return "Blue"
return "Norwegian %s" % colour()


def cardinal(x):
def colour():
return "crimson"
return "Cardinal Fang wears a %s robe" % colour()


Except of course that's a trivially silly example. (For the sake of the
argument, let's pretend the two functions colour() do actual
calculations.)

These days, I'd write them something like this:

def parrot_colour():
return "Blue"

def cardinal_colour():
return "crimson"

def parrot():
return "Norwegian %s" % parrot_colour()

def cardinal(x):
return "Cardinal Fang wears a %s robe" % cardinal_colour()


These days, almost the only time I use nested functions is for function
factories.

--
Steven

Steven D'Aprano

unread,
Dec 2, 2008, 6:06:08 AM12/2/08
to
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?

--
Steven

Duncan Booth

unread,
Dec 2, 2008, 7:52:55 AM12/2/08
to
Steven D'Aprano <st...@REMOVE-THIS-cybersource.com.au> wrote:

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

Provided it doesn't take any arguments you can run the code with exec or
eval:

>>> def outer():
def inner():
print "inner called"
return inner()

>>> eval(outer.func_code.co_consts[1])
inner called

More usefully you could use the code object to construct another function,
e.g. if you wanted to change the values of some default arguments.

>>> types.FunctionType(outer.func_code.co_consts[1], globals())()
inner called


--
Duncan Booth http://kupuguy.blogspot.com

Neal Becker

unread,
Dec 2, 2008, 2:56:00 PM12/2/08
to pytho...@python.org
Robert Kern wrote:

> Neal Becker wrote:
>> Arnaud Delobelle wrote:
>>

>> If it's only used inside.
>

> I, for one, find that significantly less clear. I only expect functions to
> be defined inside of functions if they are going to use lexical scoping
> for some reason. If I read your code, I'd probably waste a good five
> minutes trying to figure out what part of the local scope you were using
> before I would conclude that you just did it because you thought it looked
> better.
>

I'm using the inner function to prevent pollution of the global namespace. Local variables also have this attribute. Code is easier to understand when it is written with the greatest locality - so you can see immediately that the inner function isn't used somewhere else.

Robert Kern

unread,
Dec 2, 2008, 4:11:15 PM12/2/08
to pytho...@python.org
Neal Becker wrote:
> Robert Kern wrote:
>
>> Neal Becker wrote:
>>> Arnaud Delobelle wrote:
>>>
>>> If it's only used inside.
>> I, for one, find that significantly less clear. I only expect functions to
>> be defined inside of functions if they are going to use lexical scoping
>> for some reason. If I read your code, I'd probably waste a good five
>> minutes trying to figure out what part of the local scope you were using
>> before I would conclude that you just did it because you thought it looked
>> better.
>
> I'm using the inner function to prevent pollution of the global namespace. Local variables also have this attribute. Code is easier to understand when it is written with the greatest locality - so you can see immediately that the inner function isn't used somewhere else.

I don't think that the greatest locality metric is the only factor in
understandability. You're introducing more nesting, which means more context
switching as I read the code. I like shortish functions with one coherent idea
per function. I like to see the argument spec, the docstring, and the body of
the code all on one page. If the functions it calls are reasonably well-named,
or their calls are commented, I can read that function all the way through
without having to page around. By nesting the definitions of the functions
inside, I have to skip from the argument spec and docstring down to the body.

And I'm still going to spend time trying to figure out what lexical scoping you
are using before giving up. I still think that defining a function inside of a
function for organizational purposes is a (mild) abuse of the feature. Packages,
modules, __all__, and the _underscoring conventions are the language features
for organizing namespaces of functions.

Carl Banks

unread,
Dec 2, 2008, 5:43:47 PM12/2/08
to
On Dec 2, 1:56 pm, Neal Becker <ndbeck...@gmail.com> wrote:
> Robert Kern wrote:
> > Neal Becker wrote:
> >> Arnaud Delobelle wrote:
>

Let me throw this out:

The rule-of-thumb should not be whether something is used anywhere
else, but rather if it is modified locally or depends on something
that exists locally. Use outer scope when possible, inner scope only
when necessary. Flat is better than nested.

In this case, local variables are different from locally-defined
functions, because local variables are typically rebound over the
course of the function, or are constant but depend on the function's
arguments. (Variables that aren't rebound within the function, or
don't depend on the arguments, are considered constants and are
typically defined in all caps at the module level.) Locally-defined
functions, however, are constant, and unless they use values from the
enclosing scope, they do not depend on the local environment.
Therefore, by this criterion, the locally-defined function ought to be
defined at the module level, just like constants are.

I don't buy that nesting functions prevents namespace pollution.
IMHO, namespace pollution occurs when you do things like "from module
import *"; there is no pollution when you control the whole namespace
yourself.


Having said that, I do use local functions without closures here and
there, when it's something either too silly or too specific to
introduce a module-level function for. Judgment call, mostly, but
typically I go to the module level.


Carl Banks

Carl Banks

unread,
Dec 2, 2008, 6:47:03 PM12/2/08
to
On Dec 1, 11:41 am, Neal Becker <ndbeck...@gmail.com> wrote:
> I guess I've become accustomed to decent compilers performing
> reasonable transformations and so have tended to write code for
> clarity.

Python isn't that language. It'll do what it can, but a very
aggressive optimizing compiler wouldn't be possible with Python's
dynamicism, at least not without a lot of effort (both man and
computer).

Python does not actually compile the function every invocation, but it
does create a function object from the compiled code object, which
does take a bit of time. If it is a concern, run it through a
profiler to get an idea of how much it costs, so you can make an
informed decision.


Carl Banks

0 new messages