I don't know if I'm missing something, but there's a behavior of functools.lru_cache() that I currently don't understand.
As the documentation states:
"If typed is set to true, function arguments of different types will be cached separately. For example, f(3) and f(3.0) will be treated as distinct calls with distinct results."
For a function accepting only positional arguments, using typed=False doesn't seem to be working in all cases.
>>> import functools
>>>
>>> @functools.lru_cache() # Implicitly uses typed=False
>>> def func(a):
... return a
>>>
>>> func(1)
>>> func(1.0)
>>>
>>> print(func.cache_info())
CacheInfo(hits=0, misses=2, maxsize=128, currsize=2)
Instead, I would have expected: CacheInfo(hits=1, misses=1, maxsize=128, currsize=2)
So it looks like 1 and 1.0 were stored as different values even though typed=False was used.
After analyzing the source code of _functoolsmodule.c::lru_cache_make_key(), I found what follows:
if (!typed && !kwds_size) {
if (PyTuple_GET_SIZE(args) == 1) {
key = PyTuple_GET_ITEM(args, 0);
if (PyUnicode_CheckExact(key) || PyLong_CheckExact(key)) { <<< it appears that a 'float' would cause 'args' (a tuple) to be returned as the key, whereas an 'int' would cause 'key'
/* For common scalar keys, save space by (an int) to be returned as the key. So 1 and 1.0 generate different hashes and are stored as different items.
dropping the enclosing args tuple */
Py_INCREF(key);
return key;
}
}
Py_INCREF(args);
return args;
}
At some point in the past, the above code section looked like this:
if (!typed && !kwds) {
Py_INCREF(args);
return args;
}
So no matter what the type of the argument was, it was working.
Am I somehow mistaken in my analysis or is this a bug?
|