Delayed evaluation and setdefault()
Peter Otten
__peter__ at web.de
Mon Jan 19 15:03:51 EST 2004
Leo Breebaart wrote:
> Hi all,
>
> I have a question about Python and delayed evaluation.
>
> Short-circuiting of Boolean expressions implies that in:
>
> >>> if a() and b():
>
> any possible side-effects the call to b() might have will not
> happen of a() returns true, because then b() will never be
> executed.
>
> However, if I go looking for dictionary keys like this:
>
> >>> d = {}
> >>> d.setdefault('foo', b())
>
> then b() *does* get executed regardless of whether or not the
> value it returns is actually used ('foo' was not found) or not
> ('foo' was found).
>
> If b() has side-effects, this may not be what you want at all. It
> would seem to me not too exotic to expect Python to have some
> sort of construct or syntax for dealing with this, just as it
> supports Boolean short-circuiting.
>
> So I guess my question is twofold: one, do people think
> differently, and if so why?; and two: is there any elegant (or
> other) way in which I can achieve the delayed evaluation I desire
> for setdefault, given a side-effect-having b()?
>
Lazy evaluation is dangerous as the state of mutable objects may have
changed in the mean time. In your case
if "foo" not in d:
d["foo"] = b()
should do.
However, if you absolutely want it, here's a slightly more general approach:
<defer.py>
class Defer:
def __init__(self, fun, *args):
self.fun = fun
self.args = args
def __call__(self):
""" Calculate a deferred function the first time it is
invoked, return the stored result for subsequent calls
"""
try:
return self.value
except AttributeError:
self.value = self.fun(*self.args)
return self.value
def defer(fun, *args):
""" Use, e. g., defer(average, 1, 2) to delay the calculation
of the average until the first time undefer() is called
with the Defer instance.
"""
return Defer(fun, *args)
def undefer(value):
""" Check if value is deferred, if so calculate it, otherwise
return it unchanged
"""
if isinstance(value, Defer):
return value()
return value
#example usage:
class Dict(dict):
def setdefault(self, key, value):
try:
return self[key]
except KeyError:
# it might make sense to further delay the
# calculation until __getitem__()
self[key] = value = undefer(value)
return value
def b(value):
print "side effect for", value
return value
dv = defer(b, "used three times")
d = Dict()
d.setdefault("x", defer(b, "a1"))
d.setdefault("x", defer(b, "a2"))
d.setdefault("y", "b")
d.setdefault("a", dv)
d.setdefault("b", dv)
d.setdefault("c", dv)
print ",\n".join(repr(d).split(","))
assert d["a"] is d["b"]
</defer.py>
You will still have to bother with undeferring in *many* places in your
program, so the above would go into the "or other" category. For lazy
evaluation to take off, I think it has to be build into the language. I
doubt, though, that much effort will be saved, as a human is normally lazy
enough to not calculate things until absolutely needed.
Peter
More information about the Python-list
mailing list