Unexpected Python Behavior

Alex Martelli aleaxit at yahoo.com
Sun Sep 26 04:39:15 EDT 2004


Andrea Griffini <agriff at tin.it> wrote:

> On Fri, 24 Sep 2004 14:48:37 +0100, Michael Hoffman
> <m.h.3.9.1.without.dots.at.cam.ac.uk at example.com> wrote:
> 
> >I think you're missing the usefulness of this feature. Go back to the
> >link you included and read the next paragraph, "This feature can be useful."
> 
> Given that now functions can have attributes, wouldn't be better
> stop pushing ugly, risky and cryptic syntax for poor's man static ?

I think it's in fact very nice syntax:

def f(x, cache=[]):
    if x in cache: ...

vs your apparently implied suggestion of:

def f(x):
    if x in f.cache: ...
f.cache = []

which among other things suffers from f.cache having to be used in spots
that come lexically before it's defined; or a decorator equivalent:

@ set_function_attributes(cache=[])
def f(x):
    if x in f.cache: ...

As for 'risky', both approaches are.  The default argument risks the
user mistakenly passing a corresponding actual-argment; the function
attribute risks the user rebinding the name.  It just don't work when
the function name aint'a global, as in a closure; nor does it work
equivalently for an overriden method, e.g.:

class base:
    def f(self, x, cache=[]):
        if x in cache: ...

class derived(base):
    def f(self, x, y, cache=[]):
        if y not in cache:
            return base.f(self, x)

here you get two separate caches, one for base.f and one for derived.f,
no sweat -- and if you want base.f to use derived.f's cache when call
from there, just chance the last call to 'base.f(self, x, cache)' --
obvious, simple, elementary.

See now what happens with the use of 'self.f.cache' or 'base.f.cache':
how do you get two separate caches, or get to share one, as easily as
with the normal approach?  You're forcing base's designer to decide for
all derived classes, the normal approach is clearly more flexible.

And good luck in explaining all this to beginners -- while the default
argument approach is really trivial to explain.  Simple is better than
complex, and a general technique like using default values for caching
is better than a technique based on attributes which is not general
enough to be just used everywhere.

_Plus_, teaching this use of default values to beginners helps them
understand how default values work in all cases.  Explaining the dirty
tricks needed to extend a bit the applicability of using function
attributes for caching offers no such nice "side advantage".


> IMO one thing is backward compatibility, another is pushing the
> uglyness in the future for no good reasons.

I think there are plenty of good reasons, see above.

In addition, accessing a local name is of course way faster than
accessing a global name and then an attribute thereof.  In 2.3, I
repeatably measure 1.75 vs 2.50 usec; in 2.4, 1.25 vs 1.84.  If that was
all the local-argument-default advantage, one could quibble, but coming
on top of the above-mentioned arguments, I think it's nice too.


> I am talking about the python docs that everywhere are apparently
> advocating this approach for local cache implementation. Is this
> doing any good for newbies approaching python ?

Yes, it helps them a lot to understand, realize, and remember, that
default values are shared among all of a function's calls.
 
> Or may be this is more pythonic ? If yes... why ?

It's simpler and more general.

If I were to use a decorator, I'd rather have it inject 'static locals'
in some way that doesn't let them be optionally passed as arguments, but
still in local namespace -- less flexible than the plain good old
technique, but avoids "accidental argument-passing" and may be nice when
you need to support *args.  I think the implementation of such a
decorator is a bit messy, though, and besides flexibility you lose the
nice educational side effect.  So, I prefer the status quo in this case.


Alex



More information about the Python-list mailing list