-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cached method implementation no longer works on Python 3.7.3 #80831
Comments
In this ticket, I learned that jaraco.functools.method_cache no longer works on Python 3.7.3. A distilled version of what's not working is this example:
The second call to the cached function is missing the cache even though the parameters to the function are the same.
Here's a further distilled example not relying on any code from jaraco.functools:
I was not able to replicate the issue with a simple lru_cache on a partial object:
Suggesting that there's some interaction with the instance attribute and the caching functionality. I suspect the issue arose as a result of changes in bpo-35780. |
I've put together this full reproducer script:
That code fails on Python 3.7.3. If I bypass the |
It seems highly likely this is related to bpo-26110 which optimizes method calls by avoiding the creation of bound methods in certain circumstances, and/or the follow-up bpo-29263. Side-note: bound_method = functools.partial(method, self) is not how you create real bound methods. The correct approach (that makes a bound method identical to what the interpreter makes when necessary) for your use case is: bound_method = types.MethodType(method, self) |
Hmm... Although I can't seem to reproduce on 3.7.2 (ob.calls is 1), so assuming it is really happening in 3.7.3, it wouldn't be either of those issues (which were fully in place long before 3.7.2 I believe). |
Hi Josh. Thanks for the tip on types.MethodType. I've updated the code to use that and the behavior seems to be the same, MethodType does seem a more appropriate way to create a bound method. Regarding the referenced tickets, I suspect they're not pertinent, as the behavior did work in Python 3.7.1 (confirmed) and 3.7.2 (highly likely), so the regression appears to be in the changes for 3.7.3 (https://docs.python.org/3.7/whatsnew/changelog.html#python-3-7-3-final). |
Thanks for the reproducer code. I've bisected this back to b2b023c which fixes other known bugs in the lru_cache in bpo-35780. The problem is due to the lines that use a scalar instead of an args tuple for exact ints and strs. I'll work-up a PR to fix it soon (I'm on vacation and have limited connectivity so it may take a few days). |
Nice work. Thanks Raymond. |
For the record, here's what is going on. The method_cache() code uses a slightly different invocation for the first call than for subsequent calls. In particular, the wrapper() uses **kwargs with an empty dict whereas the first call didn't use keyword arguments at all. The C version of the lru_cache() is treating that first call as distinct from the second call, resulting in a cache miss for the both the first and second invocation but not in subsequent invocations. The pure python lru_cache() has a memory saving fast path taken when kwds is an empty dict. The C version is out-of-sync with because it runs the that path only when kwds==NULL and it doesn't check for the case where kwds is an empty dict. Here's a minimal reproducer: @lru_cache()
def f(x):
pass
f(0)
f(0, **{})
assert f.cache_info().hits == 1 Here's a possible fix: diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c
index 3f1c01651d..f118119479 100644
--- a/Modules/_functoolsmodule.c
+++ b/Modules/_functoolsmodule.c
@@ -751,7 +751,7 @@ lru_cache_make_key(PyObject *args, PyObject *kwds, int typed)
Py_ssize_t key_size, pos, key_pos, kwds_size; /* short path, key will match args anyway, which is a tuple */
- if (!typed && !kwds) {
+ if (!typed && (!kwds || PyDict_GET_SIZE(kwds) == 0)) {
if (PyTuple_GET_SIZE(args) == 1) {
key = PyTuple_GET_ITEM(args, 0);
if (PyUnicode_CheckExact(key) || PyLong_CheckExact(key)) { |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: