How to extend Python Enum?

后端 未结 7 1050
广开言路
广开言路 2020-12-08 18:26

What is best practice for extending Enum type in Python 3.4 and is there even a possibility for do this?

For example:

from enum import E         


        
7条回答
  •  天命终不由人
    2020-12-08 18:57

    I've opted to use a metaclass approach to this problem.

    from enum import EnumMeta
    
    class MetaClsEnumJoin(EnumMeta):
        """
        Metaclass that creates a new `enum.Enum` from multiple existing Enums.
    
        @code
            from enum import Enum
    
            ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
            ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
            class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
                pass
    
            print(ENUMJOINED.a)
            print(ENUMJOINED.b)
            print(ENUMJOINED.c)
            print(ENUMJOINED.d)
        @endcode
        """
    
        @classmethod
        def __prepare__(metacls, name, bases, enums=None, **kargs):
            """
            Generates the class's namespace.
            @param enums Iterable of `enum.Enum` classes to include in the new class.  Conflicts will
                be resolved by overriding existing values defined by Enums earlier in the iterable with
                values defined by Enums later in the iterable.
            """
            #kargs = {"myArg1": 1, "myArg2": 2}
            if enums is None:
                raise ValueError('Class keyword argument `enums` must be defined to use this metaclass.')
            ret = super().__prepare__(name, bases, **kargs)
            for enm in enums:
                for item in enm:
                    ret[item.name] = item.value  #Throws `TypeError` if conflict.
            return ret
    
        def __new__(metacls, name, bases, namespace, **kargs):
            return super().__new__(metacls, name, bases, namespace)
            #DO NOT send "**kargs" to "type.__new__".  It won't catch them and
            #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.
    
        def __init__(cls, name, bases, namespace, **kargs):
            super().__init__(name, bases, namespace)
            #DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older.  You'll get a
            #"TypeError: type.__init__() takes no keyword arguments" exception.
    

    This metaclass can be used like so:

    >>> from enum import Enum
    >>>
    >>> ENUMA = Enum('ENUMA', {'a': 1, 'b': 2})
    >>> ENUMB = Enum('ENUMB', {'c': 3, 'd': 4})
    >>> class ENUMJOINED(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMA, ENUMB)):
    ...     e = 5
    ...     f = 6
    ...
    >>> print(repr(ENUMJOINED.a))
    
    >>> print(repr(ENUMJOINED.b))
    
    >>> print(repr(ENUMJOINED.c))
    
    >>> print(repr(ENUMJOINED.d))
    
    >>> print(repr(ENUMJOINED.e))
    
    >>> print(repr(ENUMJOINED.f))
    
    

    This approach creates a new Enum using the same name-value pairs as the source Enums, but the resulting Enum members are still unique. The names and values will be the same, but they will fail direct comparisons to their origins following the spirit of Python's Enum class design:

    >>> ENUMA.b.name == ENUMJOINED.b.name
    True
    >>> ENUMA.b.value == ENUMJOINED.b.value
    True
    >>> ENUMA.b == ENUMJOINED.b
    False
    >>> ENUMA.b is ENUMJOINED.b
    False
    >>>
    

    Note what happens in the event of a namespace conflict:

    >>> ENUMC = Enum('ENUMA', {'a': 1, 'b': 2})
    >>> ENUMD = Enum('ENUMB', {'a': 3})
    >>> class ENUMJOINEDCONFLICT(Enum, metaclass=MetaClsEnumJoin, enums=(ENUMC, ENUMD)):
    ...     pass
    ...
    Traceback (most recent call last):
      File "", line 1, in 
      File "", line 19, in __prepare__
      File "C:\Users\jcrwfrd\AppData\Local\Programs\Python\Python37\lib\enum.py", line 100, in __setitem__
        raise TypeError('Attempted to reuse key: %r' % key)
    TypeError: Attempted to reuse key: 'a'
    >>>
    

    This is due to the base enum.EnumMeta.__prepare__ returning a special enum._EnumDict instead of the typical dict object that behaves different upon key assignment. You may wish to suppress this error message by surrounding it with a try-except TypeError, or there may be a way to modify the namespace before calling super().__prepare__(...).

提交回复
热议问题