diff -r 502c8b7e8ad2 Doc/library/enum.rst --- a/Doc/library/enum.rst Fri Feb 21 09:27:17 2014 +0100 +++ b/Doc/library/enum.rst Fri Feb 21 02:27:56 2014 -0800 @@ -367,20 +367,23 @@ Enumerations can be pickled and unpickle The usual restrictions for pickling apply: picklable enums must be defined in the top level of a module, since unpickling requires them to be importable from that module. .. note:: With pickle protocol version 4 it is possible to easily pickle enums nested in other classes. +It is possible to modify how Enum members are pickled/unpickled by defining +:meth:`__reduce_ex__` in the enumeration class. + Functional API -------------- The :class:`Enum` class is callable, providing the following functional API:: >>> Animal = Enum('Animal', 'ant bee cat dog') >>> Animal >>> Animal.ant diff -r 502c8b7e8ad2 Lib/enum.py --- a/Lib/enum.py Fri Feb 21 09:27:17 2014 +0100 +++ b/Lib/enum.py Fri Feb 21 02:27:56 2014 -0800 @@ -108,28 +108,34 @@ class EnumMeta(type): # create our new Enum type enum_class = super().__new__(metacls, cls, bases, classdict) enum_class._member_names_ = [] # names in definition order enum_class._member_map_ = OrderedDict() # name->value map enum_class._member_type_ = member_type # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} - # check for a supported pickle protocols, and if not present sabotage - # pickling, since it won't work anyway. - # if new class implements its own __reduce_ex__, do not sabotage - if classdict.get('__reduce_ex__') is None: + # check for supported pickle protocols in a mixed-in type, and if not + # present sabotage pickling; if we don't do this, pickle.dumps will + # succeed, but pickle.loads will fail, and it's better to have the + # failure close to the problem instead of far away. + # + # if the new class implements its own __reduce_ex__, do not sabotage + # -- it's on them to make sure it works correctly. We use __reduce_ex__ + # instead of any of the others as it is preferred by pickle over + # __reduce__, and it handles all pickle protocols. + if '__reduce_ex__' not in classdict: if member_type is not object: methods = ('__getnewargs_ex__', '__getnewargs__', '__reduce_ex__', '__reduce__') - if not any(map(member_type.__dict__.get, methods)): + if not any(m in member_type.__dict__ for m in methods): _make_class_unpicklable(enum_class) # instantiate them, checking for duplicates as we go # we instantiate first instead of checking for duplicates first in case # a custom __new__ is doing something funky with the values -- such as # auto-numbering ;) for member_name in classdict._member_names: value = members[member_name] if not isinstance(value, tuple): args = (value, ) @@ -186,28 +192,36 @@ class EnumMeta(type): enum_class.__new__ = Enum.__new__ return enum_class def __call__(cls, value, names=None, *, module=None, qualname=None, type=None): """Either returns an existing member, or creates a new enum class. This method is used both when an enum class is given a value to match to an enumeration member (i.e. Color(3)) and for the functional API (i.e. Color = Enum('Color', names='red green blue')). - When used for the functional API: `module`, if set, will be stored in - the new class' __module__ attribute; `qualname`, if set, will be stored - in the new class' __qualname__ attribute; `type`, if set, will be mixed - in as the first base class. + When used for the functional API: - Note: if `module` is not set this routine will attempt to discover the - calling module by walking the frame stack; if this is unsuccessful - the resulting class will not be pickleable. + `value` will be the name of the new class. + + `names` should be either a string of names (values will start at 1), or + an iterator/mapping of name, value pairs. + + `module` should be set to the module this class is being created in; + if it is not set, an attempt to find that module will be made, but if + it fails the class will not be picklable. + + `qualname` should be set to the actual location this class can be found + at in its module; by default it is set to the global scope. If this is + not correct, unpickling will fail in some circumstances. + + `type`, if set, will be mixed in as the first base class. """ if names is None: # simple value lookup return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type return cls._create_(value, names, module=module, qualname=qualname, type=type) def __contains__(cls, member): return isinstance(member, cls) and member.name in cls._member_map_