Supports in-process memoization out-of-the-box as well as more robust caches,
since backends are API compliant with
Django,
Flask, and
Werkzeug.
It is also trivial to write your own cache backend if your implementation of
choice is not already supported.

Caveats

All argument values must implement the __hash__ method, and this method
must be deterministic
between interpreter restarts, etc. This makes it impossible to use lists or
dictionaries as arguments to a cached function, unless you convert the values
into a hashable type before calling the cache-wrapped function.

Some types (specifically, user defined class instances) are hashable, but not
deterministic (their hash value evaluates to their id() -- in other words,
their memory address) which can at best result in cache misses, and at worst
result in hash collisions and invalid results. Please take the time to
understand the __hash__ implementation of argument types that may be
passed to your cached functions, and perhaps consider writing your own
decorator that implements type checking to increase safety.

Refactoring can result in different generated bytecode and source
modifications will result in a different hash values, even if the underlying
logic of the function is the same. For example, changing an if/else
statement to a ternary statement will appear as a new version of the function.

Introspection of both bytecode and source code is limited to the the cached
function itself, and doesn't provide any versioning of external functions.

Bytecode versioning-specific caveats

Bytecode is not guaranteed to be the same between different interpreter
versions or implementations (for example, Python 2.6 to 2.7, or CPython to
PyPy.) In many cases the bytecode will be identical, but not always.