Skip to content
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

Classes with cached_property can't be cloudpickled #438

Open
durumu opened this issue Aug 31, 2021 · 3 comments
Open

Classes with cached_property can't be cloudpickled #438

durumu opened this issue Aug 31, 2021 · 3 comments

Comments

@durumu
Copy link

durumu commented Aug 31, 2021

I believe this is a bug.

Here is an example:

from functools import cached_property
import pickle
import cloudpickle


class MyExample:
    def __init__(self, foo):
        self.foo = foo

    @cached_property
    def bar(self) -> int:
        return self.foo * 3


example = MyExample(2)
pickle.dumps(example)      # works
cloudpickle.dumps(example) # crashes

This fails with TypeError: cannot pickle '_thread.RLock' object.

@pierreglaser
Copy link
Member

pierreglaser commented Sep 12, 2021

Hii @durumu,

pickling cached objects such as function/methods/properties is not yet supported by cloudpickle (see #178).

A possible minimal fix (that would not attempt at pickling the cache contents when pickling the cached object, see #178 (comment)) should be relatively simple to implement, PR welcome.

@Hoeze
Copy link

Hoeze commented Jan 9, 2023

This seemingly also does not work for the class definition itself:

from functools import cached_property
import cloudpickle


class MyExample:
    def __init__(self, foo):
        self.foo = foo

    @cached_property
    def bar(self) -> int:
        return self.foo * 3


cloudpickle.dumps(MyExample) # fails

@praateekmahajan
Copy link

Ran into a similar issue, inspired from this wrote similar functionality that uses copyreg to pickle cached_property objects

from functools import cached_property


# This function is used to extract the necessary information to pickle a cached_property object.
def pickle_cached_property(obj: cached_property):  # noqa: ANN202
    # The `func` attribute of a cached_property object is the original method
    # that was decorated with @cached_property.
    # The `__globals__` attribute of that function contains a reference to the
    # global namespace where the function was defined, which may include the owner class.

    for cls in obj.func.__globals__.values():
        # Check if the value is a class (owner of the cached_property).
        if isinstance(cls, type):
            for name, prop in cls.__dict__.items():
                # Check if the property matches the cached_property we're trying to pickle.
                if prop is obj:
                    return (unpickle_cached_property, (obj.func, cls, name))

    # If the loop completes without returning, it means the owner class wasn't found.
    err = f"Could not find the owner class for cached_property {obj}"
    raise TypeError(err)


# Define a function to reconstruct the cached_property object during unpickling
def unpickle_cached_property(func, owner, name):  # noqa: ANN202;
    # Create a new cached_property object with the original function
    prop = cached_property(func)
    # The `__set_name__` method of a cached_property is normally called automatically
    # when the property is assigned to a class attribute. However, since we are
    # reconstructing the object during unpickling, we need to call it manually.
    # This method sets up the internal state of the cached_property object
    # so that it can function correctly when accessed as a class attribute.
    prop.__set_name__(owner, name)
    return prop


# Register the pickling and unpickling functions for the cached_property class
copyreg.pickle(cached_property, pickle_cached_property, unpickle_cached_property)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants