It gets complicated as soon as there’s a pointer involved
Do you compare the pointer for equality, or do you follow it and deep-compare what’s on the other side?
What about the next pointer jump? What if there’s a class instance? What if there’s a reference cycle?
Python lets you implement custom logic in __eq__() to manually bridge some of these gaps, but doing that for everything can get out of hand. Also, deeply-traversing large objects to check equality can get arbitrarily expensive. Immutable structures simplify these comparisons (just compare everything as a value, any change will mean a new pointer), but obviously introduce certain constraints.
By tracking observed properties of mutable structures at runtime, and then publishing cache invalidations only when they change, you can:
- Do no extra work to know whether or not a cache has been invalidated
- Automatically and perfectly track everything the function “cares about”
- Track only the things it cares about, so changes to properties it doesn’t actually use won’t invalidate the cache