Unexpected Python Behavior

Bengt Richter bokr at oz.net
Tue Sep 28 22:42:30 CEST 2004


On Tue, 28 Sep 2004 19:12:16 +0200, aleaxit at yahoo.com (Alex Martelli) wrote:
[...]
>> >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.
>
Thanks for highlighting this. It is one of my pet peeves that there is
no local "self" reference available for a function or class etc., and
no way (other than one-at-a-time method-self binding) to get a def-time
expression value bound to a locally visible name. Maybe we could have
a triple star in the arg list to delimit syntax and effect like defaults
but which don't become part of the arg count? E.g.,

    def f(x, *** cache={}, pi = __import__('math').pi):
        try: return cache[x]
        ...etc

Or arrange it differently, and add comments

    def f(x, ***
        # presets
        cache={},      # XXX lru later ;-)
        pi = __import__('math').pi
    ):  # run time
        try: return cache[x]
        ...etc
>
>> 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.
>
I'm not sure why you didn't mention the alternative of not simulating
but actually using the mechanism of method binding, e.g.,

 >>> import math
 >>> def f(cache, x):
 ...     try: return cache[x]
 ...     except KeyError:
 ...         cache[x] = result = math.sin(x)
 ...         return result
 ...
 >>> f = f.__get__({}, dict)
 >>> f
 <bound method dict.f of {}>
 >>> f(math.pi/6.)
 0.49999999999999994
 >>> f.im_self
 {0.52359877559829882: 0.49999999999999994}
 >>> f(0.0)
 0.0
 >>> f.im_self
 {0.52359877559829882: 0.49999999999999994, 0.0: 0.0}

And you can't pass extra arguments ...  
(BTW, (to OP) note that f sees bound "self" (the cache) as part of the arg count,
the way f the function sees it. This would have been be clearer if I had not rebound 'f')

 >>> f({}, 0.0)
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: f() takes exactly 2 arguments (3 given)

Regards,
Bengt Richter



More information about the Python-list mailing list