optimization

N

Neal Becker

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.
 
A

Arnaud Delobelle

Neal Becker said:
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.

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

Robert Kern

Neal said:
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
 
B

bearophileHUGS

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
 
R

Robert Kern

Robert Kern:

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.

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

Steven D'Aprano

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.
 
S

Steven D'Aprano

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 inner():
.... return None
.... return inner()
........ return None
........ 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.
(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?
 
N

Neal Becker

Robert said:
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.
 
R

Robert Kern

Neal said:
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.

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

Carl Banks

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.

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
 
C

Carl Banks

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,431
Messages
2,571,677
Members
48,796
Latest member
Greg L.

Latest Threads

Top