Establecer automáticamente el valor de un miembro enum a su nombre

He estado jugando con la biblioteca de enumeración de python y he encontrado un enigma. En los documentos, muestran un ejemplo de una enumeración de numeración automática , en la que se define algo:

class Color(AutoNumber): red = () green = () ... 

Quiero hacer una clase similar, pero el valor se establecería automáticamente a partir del nombre del miembro Y mantendrá la funcionalidad que se obtiene al hacer las cosas str y enum mixin

Así que algo como:

 class Animal(MagicStrEnum): horse = () dog = () Animal.dog == 'dog' # True 

He mirado el código fuente del módulo de enumeración y he probado muchas variaciones jugando con __new__ y la clase EnumMeta

Actualización: 2017-03-01

En Python 3.6 (y Aenum 2.0 1 ) se han agregado las clases Flag e IntFlag ; parte de eso fue un nuevo auto() helper que hace esto trivialmente fácil:

 >>> class AutoName(Enum): ... def _generate_next_value_(name, start, count, last_values): ... return name ... >>> class Ordinal(AutoName): ... NORTH = auto() ... SOUTH = auto() ... EAST = auto() ... WEST = auto() ... >>> list(Ordinal) [, , , ] 

Respuesta original

La dificultad con una clase AutoStr es que el nombre del miembro de la enumeración no se pasa al código que lo crea, por lo que no está disponible para su uso. Otro problema es que str es inmutable, por lo que no podemos cambiar esos tipos de enumeraciones después de que se hayan creado (utilizando un decorador de clase , por ejemplo).

Lo más fácil de hacer es usar la API funcional :

 Animal = Enum('Animal', [(a, a) for a in ('horse', 'dog')], type=str) 

lo que nos da:

 >>> list(Animal) [, ] >>> Animal.dog == 'dog' True 

La siguiente cosa más fácil de hacer, suponiendo que desea hacer una clase base para su uso futuro de enumeración, sería algo como mi DocEnem :

 class DocEnum(Enum): """ compares equal to all cased versions of its name accepts a doctring for each member """ def __new__(cls, *args): """Ignores arguments (will be handled in __init__)""" obj = object.__new__(cls) obj._value_ = None return obj def __init__(self, doc=None): # first, fix _value_ self._value_ = self._name_.lower() self.__doc__ = doc def __eq__(self, other): if isinstance(other, basestring): return self._value_ == other.lower() elif not isinstance(other, self.__class__): return NotImplemented return self is other def __ne__(self, other): return not self == other 

y en uso:

 class SpecKind(DocEnum): REQUIRED = "required value" OPTION = "single value per name" MULTI = "multiple values per name (list form)" FLAG = "boolean value per name" KEYWORD = 'unknown options' 

Tenga en cuenta que, a diferencia de la primera opción, los miembros de DocEnum no son DocEnum .


Si quiere hacerlo de la manera más difícil: subclase EnumMeta y juegue con el nuevo diccionario de clases de Enum antes de crear los miembros:

 from enum import EnumMeta, Enum, _EnumDict class StrEnumMeta(EnumMeta): def __new__(metacls, cls, bases, oldclassdict): """ Scan through `oldclassdict` and convert any value that is a plain tuple into a `str` of the name instead """ newclassdict = _EnumDict() for k, v in oldclassdict.items(): if v == (): v = k newclassdict[k] = v return super().__new__(metacls, cls, bases, newclassdict) class AutoStrEnum(str, Enum, metaclass=StrEnumMeta): "base class for name=value str enums" class Animal(AutoStrEnum): horse = () dog = () whale = () print(Animal.horse) print(Animal.horse == 'horse') print(Animal.horse.name, Animal.horse.value) 

Lo que nos da:

 Animal.horse True horse horse 

1 Divulgación: Soy el autor de Python stdlib Enum , el enum34 backport y la biblioteca de enumeración avanzada ( aenum ) .

Quizás esté buscando el atributo de name que es proporcionado automáticamente por la clase Enum

 >>> class Animal(Enum): ... ant = 1 ... bee = 2 ... cat = 3 ... dog = 4 ... >>> Animal.ant.name == "ant" True 

Aunque si realmente quieres dispararte en el pie. Y estoy seguro de que esto introducirá todo un mundo de errores (he eliminado el más obvio).

 from enum import Enum, EnumMeta, _EnumDict class AutoStrEnumDict(_EnumDict): def __setitem__(self, key, value): super().__setitem__(key, key) class AutoStrEnumMeta(EnumMeta): @classmethod def __prepare__(metacls, cls, bases): return AutoStrEnumDict() def __init__(self, name, bases, attrs): super().__init__(name, bases, attrs) # override Enum.__str__ # can't put these on the class directly otherwise EnumMeta overwrites them # should also consider resetting __repr__, __format__ and __reduce_ex__ if self.__str__ is not str.__str__: self.__str__ = str.__str__ class AutoStrNameEnum(str, Enum, metaclass=AutoStrEnumMeta): pass class Animal(AutoStrNameEnum): horse = () dog = () print(Animal.horse) assert Animal.horse == "horse" assert str(Animal.horse) == "horse" # and not equal to "Animal.horse" (the gotcha mentioned earlier)