Factorials

Alex Martelli aleax at aleax.it
Tue Sep 23 06:15:27 EDT 2003


Jacek Generowicz wrote:
   ...
> Or, you could let _any_ (keywordless) function benefit from its own
> experience:

This "_any_ (keywordless) function" is a substantial overbid.  The
following implementation:

> # General memoizer: takes a function as returns another function which
> # has the same semantics as the original, but caches previously
> # calculated values
> def memoize(callable):
>     cache = {}
>     def proxy(*args):
>         try: return cache[args]
>         except KeyError: return cache.setdefault(args, callable(*args))
>     return proxy

only works for a "PURE" function (e.g., not random.random() nor time.time(),
even though both ARE obviously part of the "any keywordless function"
set!-) *WITHOUT* unhashable arguments (e.g., not the new built-in 'sum',
even though it IS pure when called with a list argument -- because when
it tries to index cache with the list as part of the key, you'll get a
TypeError).  Basically, it's fine if arguments (if any) are numbers,
strings, or hashable tuples, but it would be pushing your luck A LOT to
use it in any other case -- it's particularly dangerous, because you
will get a silent misbehavior, in a case such as:

class Foo:
    def __init__(self, x=0, y=0):
        self.x=x
        self.y=y

def xplusy(somefoo):
    return somefoo.x + somefoo.y

f = Foo()
print xplusy(f)
f.x = 23
print xplusy(f)

# so far so good, but now...:
xplusy = memoize(xplusy)
print xplusy(f)
# still LOOKS fine, but, check this out...:
f.y = 100
print xplusy(f)
# OOPS!  xplusy's "memory" is now subtly incorrect...!!!


Unfortunately f "is hashable" (because it doesn't define a __cmp__)
but is NOT immutable, and xplusy, while pure, DOES depend on some
attributes of its (mutable) argument.  The resulting behavior may
be rather unexpected, and, indeed, a subtle bug indeed to find...


Alex





More information about the Python-list mailing list