Skip to content

Pickling enum types with cloudpickle #244

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

Closed
pcmoritz opened this issue Feb 1, 2019 · 8 comments
Closed

Pickling enum types with cloudpickle #244

pcmoritz opened this issue Feb 1, 2019 · 8 comments
Labels

Comments

@pcmoritz
Copy link
Contributor

pcmoritz commented Feb 1, 2019

Dear all,

cloudpickle currently doesn't support pickling types of type enum.EnumMeta. Here is an example to reproduce (on the latest release cloudpickle 0.7):

import cloudpickle
from enum import IntEnum

class Color(IntEnum):
    RED = 1

u = cloudpickle.dumps(Color)
cloudpickle.loads(u)

gives the error

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-1-ff791d51315b> in <module>
      6 
      7 u = cloudpickle.dumps(Color)
----> 8 cloudpickle.loads(u)

~/anaconda3/lib/python3.7/enum.py in __new__(metacls, cls, bases, classdict)
    149         # save enum items into separate mapping so they don't get baked into
    150         # the new class
--> 151         enum_members = {k: classdict[k] for k in classdict._member_names}
    152         for name in classdict._member_names:
    153             del classdict[name]

AttributeError: 'dict' object has no attribute '_member_names'

Any help with this is appreciated!

-- Philipp.

@ogrisel
Copy link
Contributor

ogrisel commented Feb 4, 2019

This is a bug because the same code works with pickle.dumps / pickle.loads.

@pierreglaser
Copy link
Member

For this specific case, Enum classes derive from a custom metaclass, enumMeta, that has a __new__ method breaking the classic type.__new__ signature (it requires classdict to be an _EnumDict instead of a dict)

In general, cloudpickle does not currenty provide support for classes with metaclasses that break the type.__new__ signature. We can add a hook for Enum classes and subclasses as they are part of the standard library.

@ogrisel
Copy link
Contributor

ogrisel commented Feb 5, 2019

By hook you mean a dedicated save_enum method registered in the CloudPickler.dispatch registry?

@pierreglaser
Copy link
Member

pierreglaser commented Feb 5, 2019

by hook I mean a special handling in save_dynamic_class when type(obj) is not type, but enum.EnumMeta.

EDIT: better use type.new_class as stated below.

@pierreglaser
Copy link
Member

Some updates: EnumMeta breaks type.__new__, but type.__new__ this is not the right way to create classes anyway.
The right way to create new classes is to be PEP 3115 compliant and to use types.new_class. EnumMeta for example is PEP3115 compliant, so implementing this mechanism would solve this issue.

A simple example:

In [1]: import enum                                                                                                                                                                                                                           
                                                                                                                                                                                                                                              
In [2]: import types                                                                                                                                                                                                                          
                                                                                                                                                                                                                                              
In [3]: type(enum.IntEnum)                                                                                                                                                                                                                    
Out[3]: enum.EnumMeta                                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
In [4]: Color = types.new_class('Color', bases=(enum.IntEnum,), kwds=None, exec_body=None)                                                                                                                                                    
                                                                                                                                                                                                                                              
In [5]: type(Color)                                                                                                                                                                                                                           
Out[5]: enum.EnumMeta                                                                                                                                                                                                                         
                                                                                                                                                                                                                                              
In [6]: Color                                                                                                                                                                                                                                 
Out[6]: <enum 'Color'>                                                                                                                                                                                                                        
                                           
In [7]: issubclass(Color, enum.IntEnum)                                                                                                                                                                                                       
Out[7]: True                                                                                                                                                                                                       

@ogrisel
Copy link
Contributor

ogrisel commented Feb 9, 2019

@pierreglaser actually I could not make types.new_class work because of cyclic references between the Enum subclass and its instances. I have adopted another strategy based on the EnumMeta.__call__ method (the functional API of Enums) in #246.

@pierreglaser
Copy link
Member

Should this be closed now that #246 got merged?

@ogrisel
Copy link
Contributor

ogrisel commented May 29, 2019

Indeed.

@ogrisel ogrisel closed this as completed May 29, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants