Unexpected Python Behavior

Alex Martelli aleaxit at yahoo.com
Tue Sep 28 13:12:16 EDT 2004


Andrea Griffini <agriff at tin.it> wrote:

> On Sun, 26 Sep 2004 10:39:15 +0200, aleaxit at yahoo.com (Alex Martelli)
> wrote:
> 
> >vs your apparently implied suggestion of:
> >
> >def f(x):
> >    if x in f.cache: ...
> >f.cache = []
> 
> I like more...
> 
>    def f(x):
>        if not hasattr(f,"cache"):
>            f.cache = []
>        ...

This means _every_ run of f will be loaded down by this test, just to
make everything less concise...?!  Horrible trade-off, IMHO.

> The best I can think to is something like
> 
>     def f(x):
>         static cache = []
>         ...
> 
> In other languages (mostly C) there cases in which I found
> nice the idea of a local name for a globally living object.

I don't think you stand a snowball's chance in hell to make this horrid
change become part of Python (thanks be!) -- I suggest you look at other
sort-of-Pythonic languages such as (e.g.) the new Qu, which may be more
open to changes of this nature.

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

import math

def f(x):
   try: return f.cache[x]
   except KeyError:
       f.cache[x] = result = math.sin(x)
       return result
f.cache = {}

def g(x):
   try: return g.cache[x]
   except KeyError:
       g.cache[x] = result = math.cos(x)
       return result
g.cache = {}

print f(0.2), g(0.2), f(0.2), g(0.3)

# note: oops coming
f, g = g, f

print f(0.2), g(0.2), f(0.2), g(0.3)


Basically, the idea of having f use f.cache depends on the unstated
assumption that global name 'f' will forevermore refer to the same
object it used to refer to at the time f.cache was initialized and the
first few entries of f.cache were set.  You say you consider it "risky"
to use f's default attribute values (which stick to the object, not to
the name), and I reply, _THIS_ inferior idiom you advocate is the truly
risky one -- it relies on a "fixed" name<->object correspondence which
NOTHING in Python guarantees or even suggests.


> Passing a parameter to a function that, by the way, is declaring
> it *wants* it doesn't seem to me the same as messing with
> internals of something from the outside.

If you want to hint that a parameter is really 'internal' name it with a
leading underscore, that's Python's convention.


> >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.
> 
> When you need to mess with the those vars from the "outside" then
> it's clear you're not looking for a static; probably you're not
> even looking for a function as the interaction with the "state" is
> getting too important. IMO in these cases it's *way* better to
> use a class instead (may be one implementing the call interface).

And go to the huge trouble of simulating method-binding?!  Puh-LEEZE.
Using callable classes in lieu of a class's ordinary methods, when such
methods with perfectly normal Python semantics will do, is just plain
silly -- looking for complexity where no need for it exists.


> >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.
> 
> Once reading that default were evaluated at function definition
> time was enough for me; and I honestly say that I don't remember
> ever falling for this trap of modifiable default values (so far).

Good for you -- I did, a couple of times.

> However this seems happening quite frequently to novices; actually
> *very* frequently. So either all the world is wrong or this very
> specific part of the language has a problem.

Are you not part of this world, if _ALL_ the world is wrong yet you
never had the problem?  In my case, the problems were due to previous
experience with C++ -- a traumatic rite of passage which fortunately not
everybody HAS had to go through.


> >_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".
> 
> Sure there is a plus in clearly understanding that function
> definition is an executable statement in python. But using
> an unrelated arbitrary fact (that default values are evaluated
> at that time instead than at call time) isn't IMO a good example.

Nope, that's not what I said.  I said, and I repeat (and expand since
clearly it wasn't clear to you): learning to use default values for a
cache, rather than the no-advantages-whatsoever technique you advocate
of using function attributes for the same purpose, besides all other
advantages, has the one of firmly fixing the concept that default values
are evaluate once, at def time.

> >In addition, accessing a local name is of course way faster than
> >accessing a global name and then an attribute thereof.
> 
> Yeah, static are slower than necessary; and uglier also.
> 
> May be the direction could be fixing that instead of just
> pushing towards an ugly hack that just happens to work.

I don't find default values ugly.


> >Yes, it helps them a lot to understand, realize, and remember, that
> >default values are shared among all of a function's calls.
> 
> That's the wart!

So here's our deep disagreement.  I would find it an intolerable wart if
Python did _anything else_ except evaluate expressions when it meets
them, implicitly "saving them away somewhere or other" to be reevaluated
over and over AND over again -- in SOME cases (chosen HOW...?!).


> >It's simpler and more general.
> 
> To me seems an unrelated side-effect of the decision of
> when to evaluate default parameters. I'm not questioning
> the decision per se (it has pros and cons... for example
> you can't use things like "def foo(x,y,z=x+y)") but that
> using fake parameters for static is ugly and error prone.

You call the decision "the wart!" and then claim not to be questioning
it?!  I've seen hypocrisy in my life, but this ridiculous combination
sure takes the prize!


Alex



More information about the Python-list mailing list