0

I've stumbled upon something strange. Basically, I use a library that takes the urllib.parse.urljoin function and wraps it in functools.lru_cache. But it applies the lru_cache "decorator" on it in-line - dynamically, like this:

from urllib.parse import urljoin
lru_cache()(urljoin)

It works fine, until I want to go for multiprocessing (concurrent.futures), which uses pickling. Then I get a pickling error.

This is a reproducible example I made myself:

import pickle
from functools import lru_cache

# Working pickle

@lru_cache()
def foo(n):
    return n**2

a = pickle.dumps(foo)
b = pickle.loads(a)
print(b(3))

# Not working pickle

def bar(n):
    return n**2

x = lru_cache()(bar)
a = pickle.dumps(x)
b = pickle.loads(a)
print(b(3))

Output:

C:\Users\dabljues\Desktop> python .\cache_example.py
9
Traceback (most recent call last):
  File "C:\Users\dabljues\Desktop\cache_example.py", line 23, in <module>
    a = pickle.dumps(x)
_pickle.PicklingError: Can't pickle <functools._lru_cache_wrapper object at 0x00000242624C2CF0>: it's not the same object as __main__.bar

So, as you can see, pickling works when the lru_cache is applied in a decorator style, it doesn't when it's applied inline on a function.

My question is: is there a way around it? I mean foo and x are both functools._lru_cache_wrappers, so why doesn't it work?

@Edit

Those are the __module__s and __qualname__s of bar with and without lru_cache:

import pickle
from functools import lru_cache

def bar(n):
    return n**2

x = bar
y = lru_cache()(bar)
print(x.__module__, x.__qualname__)
print(y.__module__, y.__qualname__)
__main__ bar
__main__ bar
dabljues
  • 1,663
  • 3
  • 14
  • 30
  • I think that [this answer](https://stackoverflow.com/a/52186874/8276765) maybe the solution for your problem – Yedidya Rashi Apr 22 '22 at 11:19
  • Hmm, okay, I get it. So basically, if I change the `x` variable name to `bar`, it'll work. But then, `x.__module__` and `x.__qualname__` are the same as `bar.__module__` and `bar.__qualname__`, so why can't it be recreated? – dabljues Apr 22 '22 at 11:40
  • From [What can be pickled](https://docs.python.org/3.10/library/pickle.html#what-can-be-pickled-and-unpickled): _"Note that functions (built-in and user-defined) are pickled by **fully qualified name**, not by value. 2 This means that only the function name is pickled, along with the name of the module the function is defined in. Neither the function’s code, nor any of its function attributes are pickled. Thus the defining module must be importable in the unpickling environment, and the module must contain the **named** object, otherwise an exception will be raised."_ – Timus Apr 22 '22 at 12:52
  • Okay, but I pickle `bar` function with `lru_cache` and save this pickled string into an `x` variable - it doesn't work. If I just go for `x = bar`, then I can pickle it and the pickled string looks like this: `b'\x80\x04\x95\x14\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x03bar\x94\x93\x94.'` (so it like contains the `bar` name). Then how does the `lru_cache()(bar)` change this? I mean I could understand that something changes here, but why doesn't it happen with `@lru_cache` decorator used directly on the function? – dabljues Apr 22 '22 at 13:21
  • `x.__qualname__` is `bar`: Check [1](https://github.com/python/cpython/blob/3.10/Lib/pickle.py#L1056), [2](https://github.com/python/cpython/blob/3.10/Lib/pickle.py#L1061), [3](https://github.com/python/cpython/blob/3.10/Lib/pickle.py#L1069), and [4](https://github.com/python/cpython/blob/3.10/Lib/pickle.py#L1075). `foo.__qualname__` is `foo` (all good). – Timus Apr 22 '22 at 14:20
  • @Timus I actually go-to-def'd there as well :D But see the question, I've edited it. If the `__module__`s and `__qualname__`s are the same for both options: with and without `lru_cache` on `bar`, then why the on with the cache fails to be pickled? – dabljues Apr 22 '22 at 14:37
  • But `x = bar` doesn't make the objects different, whereas `y = lru_cache(bar)` does: `print(x is bar, y is bar)`. (See the last check in the `pickle` module I've linked to.) – Timus Apr 22 '22 at 14:48
  • 1
    @Timus Okay, so then it doesn't work because when the import is being made, the `__main__.bar` is different from the one I supplied to `pickle.dumps`, because the `bar` that goes to `dumps` is `lru_cache`'d, and the one that is being imported is not? Therefore different objects? – dabljues Apr 22 '22 at 19:58
  • Does this answer your question? [Pickle and decorated classes (PicklingError: not the same object)](https://stackoverflow.com/questions/52185507/pickle-and-decorated-classes-picklingerror-not-the-same-object) – sophros May 22 '22 at 02:05

0 Answers0