Unexpected Python Behavior
Bengt Richter
bokr at oz.net
Tue Sep 28 16:42:30 EDT 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