enum — Soporte para enumeraciones

Nuevo en la versión 3.4.

Código fuente: Lib/enum.py


Una enumeración es un conjunto de nombres simbólicos (miembros) vinculados a valores únicos y constantes. Dentro de una enumeración, los miembros se pueden comparar por identidad, y la enumeración en sí se puede iterar.

Nota

Caso de miembros de Enum

Debido a que las enumeraciones se usan para representar constantes, recomendamos usar nombres UPPER_CASE para los miembros de enumeración, y usaremos ese estilo en nuestros ejemplos.

Contenido del Módulo

Este módulo define cuatro clases de enumeración que se pueden usar para definir conjuntos únicos de nombres y valores: Enum, IntEnum, Flag, and IntFlag. También define un decorador, unique(), y un ayudante, auto.

class enum.Enum

Clase base para crear constantes enumeradas. Consulte la sección API Funcional para obtener una sintaxis de construcción alternativa.

class enum.IntEnum

Clase base para crear constantes enumeradas que también son sub clases de int.

class enum.IntFlag

Clase base para crear constantes enumeradas que se pueden combinar usando los operadores bitwise sin perder su membresía IntFlag. Los miembros de IntFlag también son subclases de int.

class enum.Flag

Clase base para crear constantes enumeradas que se pueden combinar utilizando las operaciones bitwise sin perder su membresía Flag.

enum.unique()

El decorador de clase Enum que garantiza que solo un nombre esté vinculado a cualquier valor.

class enum.auto

Las instancias se reemplazan con un valor apropiado para los miembros de Enum. El valor inicial comienza en 1.

Nuevo en la versión 3.6: Flag, IntFlag, auto

Creando un Enum

Las enumeraciones son creadas usando la sintaxis class, lo que las hace de fácil lectura y escritura. Un método de creación alternativo se describe en API Funcional. Para definir una enumeración, hacer una subclase Enum de la siguiente manera:

>>> from enum import Enum
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...

Nota

Valores de miembros de Enum

Los valores de los miembros pueden ser cualquier cosa: int, str, etc.. Si el valor exacto no es importante, puede usar instancias auto y se elegirá un valor apropiado para usted. Se debe tener cuidado si se mezcla auto con otros valores.

Nota

Nomenclatura

  • La clase Color es una enumeración (o enum)

  • Los atributos Color.RED, Color.GREEN, etc., son miembros de enumeración (o miembros de enum) y son funcionalmente constantes.

  • Los miembros de la enumeración tienen nombres y valores (el nombre de Color.RED es ROJO, el valor de Color.BLUE es 3, etc. )

Nota

Aunque usamos la sintaxis class para crear Enums, los Enums no son clases normales de Python. Consulte ¿En qué se diferencian las enumeraciones? para obtener más detalles.

Los miembros de la enumeración tienen representaciones de cadenas legibles para humanos

>>> print(Color.RED)
Color.RED

…mientras que su repr tiene más información

>>> print(repr(Color.RED))
<Color.RED: 1>

El tipo de un miembro de enumeración es la enumeración a la que pertenece:

>>> type(Color.RED)
<enum 'Color'>
>>> isinstance(Color.GREEN, Color)
True
>>>

Los miembros de Enum también tienen una propiedad que contiene solo su nombre del elemento

>>> print(Color.RED.name)
RED

Las enumeraciones soportan iteración, en orden de definición:

>>> class Shake(Enum):
...     VANILLA = 7
...     CHOCOLATE = 4
...     COOKIES = 9
...     MINT = 3
...
>>> for shake in Shake:
...     print(shake)
...
Shake.VANILLA
Shake.CHOCOLATE
Shake.COOKIES
Shake.MINT

Los miembros de la enumeración son hasheables, por lo que pueden usarse en diccionarios y conjuntos:

>>> apples = {}
>>> apples[Color.RED] = 'red delicious'
>>> apples[Color.GREEN] = 'granny smith'
>>> apples == {Color.RED: 'red delicious', Color.GREEN: 'granny smith'}
True

Acceso programático a los miembros de la enumeración y sus atributos

A veces es útil acceder a los miembros en enumeraciones mediante programación (es decir, situaciones en las que Color.RED no funcionará porque no se conoce el color exacto al momento de escribir el programa). Enum permite dicho acceso:

>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>

Si desea acceder a los miembros de enumeración por nombre, use el acceso a elementos:

>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>

Si tiene un miembro enum y necesita su name o value:

>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1

Duplicando miembros y valores enum

Tener dos miembros enum con el mismo nombre no es válido:

>>> class Shape(Enum):
...     SQUARE = 2
...     SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: Attempted to reuse key: 'SQUARE'

Sin embargo, se permite que dos miembros enum tengan el mismo valor. Dado que dos miembros A y B tienen el mismo valor (y A se definió primero), B es un alias de A. La búsqueda por valor del valor de A y B retornará A. La búsqueda por nombre de B también retornará A:

>>> class Shape(Enum):
...     SQUARE = 2
...     DIAMOND = 1
...     CIRCLE = 3
...     ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>

Nota

Intentar crear un miembro con el mismo nombre que un atributo ya definido (otro miembro, un método, etc.) o intentar crear un atributo con el mismo nombre que un miembro no está permitido.

Garantizando valores de enumeración únicos

Por defecto, las enumeraciones permiten múltiples nombres como alias para el mismo valor. Cuando no se desea este comportamiento, se puede usar el siguiente decorador para garantizar que cada valor se use solo una vez en la enumeración:

@enum.unique

Un decorador de class específicamente para enumeraciones. Busca una enumeración __members__ reuniendo cualquier alias que encuentre; si no se encuentra alguno se genera un ValueError con los detalles:

>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
...     ONE = 1
...     TWO = 2
...     THREE = 3
...     FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE

Usando valores automáticos

Si el valor exacto no es importante, puede usar auto:

>>> from enum import Enum, auto
>>> class Color(Enum):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> list(Color)
[<Color.RED: 1>, <Color.BLUE: 2>, <Color.GREEN: 3>]

Los valores se eligen por _generate_next_value_(), que se puede invalidar:

>>> 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)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

Nota

El objetivo del método predeterminado _generate_next_value_() es proporcionar el siguiente int en secuencia con el último int proporcionado, pero la forma en que lo hace es un detalle de implementación y puede cambiar.

Nota

El método _generate_next_value_() debe definirse antes que cualquier miembro.

Iteración

Iterar sobre los miembros de una enumeración no proporciona los alias:

>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]

El atributo especial __members__ es una asignación ordenada de solo lectura de nombres a miembros. Incluye todos los nombres definidos en la enumeración, incluidos los alias:

>>> for name, member in Shape.__members__.items():
...     name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)

El atributo __members__ se puede usar para el acceso programático detallado a los miembros de la enumeración. Por ejemplo, encontrar todos los alias:

>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']

Comparaciones

Los miembros de la enumeración se comparan por identidad:

>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True

Las comparaciones ordenadas entre valores de enumeración no son soportadas. Los miembros de Enum no son enteros (pero vea IntEnum a continuación):

>>> Color.RED < Color.BLUE
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'

Aunque, las comparaciones de igualdad se definen:

>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True

Las comparaciones con valores de no enumeración siempre se compararán no iguales (de nuevo, IntEnum fue diseñado explícitamente para comportarse de manera diferente, ver más abajo):

>>> Color.BLUE == 2
False

Miembros permitidos y atributos de enumeraciones

Los ejemplos anteriores usan números enteros para los valores de enumeración. El uso de enteros es breve y útil (y lo proporciona de forma predeterminada la API Funcional), pero no se aplica estrictamente. En la gran mayoría de los casos de uso, a uno no le importa cuál es el valor real de una enumeración. Pero si el valor es importante, las enumeraciones pueden tener valores arbitrarios.

Las enumeraciones son clases de Python y pueden tener métodos y métodos especiales como de costumbre. Si tenemos esta enumeración

>>> class Mood(Enum):
...     FUNKY = 1
...     HAPPY = 3
...
...     def describe(self):
...         # self is the member here
...         return self.name, self.value
...
...     def __str__(self):
...         return 'my custom str! {0}'.format(self.value)
...
...     @classmethod
...     def favorite_mood(cls):
...         # cls here is the enumeration
...         return cls.HAPPY
...

Después:

>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'

Las reglas para lo que está permitido son las siguientes: los nombres que comienzan y terminan con un solo guión bajo están reservados por enum y no se pueden usar; todos los demás atributos definidos dentro de una enumeración se convertirán en miembros de esta enumeración, con la excepción de métodos especiales (__str__(), __add__(), etc.), descriptores (los métodos también son descriptores) y nombres de variables listado en _ignore_.

Nota: si tu enumeración define __new__() o __init__(), los valores que se hayan dado al miembro enum se pasarán a esos métodos. Ver Planet para un ejemplo.

Subclases restringidas de Enum

Una nueva clase Enum debe tener una clase base Enum, hasta un tipo de datos concreto, y tantas clases mixin basadas en object como sean necesarias. El orden de estas clases base es:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

Además, la subclasificación de una enumeración solo está permitida si la enumeración no define ningún miembro. Entonces esto está prohibido:

>>> class MoreColor(Color):
...     PINK = 17
...
Traceback (most recent call last):
...
TypeError: Cannot extend enumerations

Pero esto es permitido:

>>> class Foo(Enum):
...     def some_behavior(self):
...         pass
...
>>> class Bar(Foo):
...     HAPPY = 1
...     SAD = 2
...

Permitir la subclasificación de enumeraciones que definen miembros conduciría a una violación de algunos invariantes importantes de tipos e instancias. Por otro lado, tiene sentido permitir compartir un comportamiento común entre un grupo de enumeraciones. (Ver OrderedEnum para un ejemplo.)

Serialización

Las enumeraciones se pueden serializar y desempaquetar:

>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True

Se aplican las restricciones habituales para la serialización (pickling): las enum seleccionables se deben definir en el nivel superior de un módulo, ya que el desempaquetado requiere que sean importables desde ese módulo.

Nota

Con la versión 4 del protocolo de serialización (pickle), es posible seleccionar fácilmente las enumeraciones anidadas en otras clases.

Es posible modificar la forma en que los miembros de Enum se serializan/desempaquetan definiendo __reduce_ex__() en la clase de enumeración.

API Funcional

La clase Enum es invocable, proporcionando la siguiente API funcional:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> Animal.ANT.value
1
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]

La semántica de esta API se parece a namedtuple. El primer argumento de la llamada a Enum es el nombre de la enumeración.

El segundo argumento es la fuente de los nombres de los miembros de la enumeración. Puede se una cadena de nombres separados por espacios en blanco, una secuencia de 2 tuplas con pares de clave/valor, o un mapeo de nombres y valores (ej. diccionario). Las últimas dos opciones permiten asignar valores arbitrarios a las enumeraciones; los otros asignan automáticamente enteros crecientes comenzando con 1 (use el parámetros start para especificar un valor de inicio diferente). Se regresa una nueva clase derivada de Enum. En otras palabras, la asignación de arriba Animal es equivalente a:

>>> class Animal(Enum):
...     ANT = 1
...     BEE = 2
...     CAT = 3
...     DOG = 4
...

La razón por la que el valor predeterminado es 1 como numero inicial y no 0 es que 0 es False en sentido booleano, pero todos los miembros enum evalúan como True.

Las enumeraciones serializadas creadas con la API funcional pueden ser complicadas ya que los detalles de implementación de la pila se usan para tratar de averiguar en qué módulo se está creando la enumeración (ej. fallará si usa una función de utilidad en un módulo separado, y también puede no funcionar en IronPython o Jython). La solución es especificar el nombre del módulo explícitamente de la siguiente manera:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)

Advertencia

Si no se suministra un module, y Enum no puede determinar que es, los miembros del nuevo Enum no se podrán desempaquetar; para mantener los errores más cerca de la fuente, la serialización se deshabilitará.

El nuevo protocolo 4 de serialización también, en ciertas circunstancias, se basa en __qualname__ se establece en la ubicación donde la serialización podrá encontrar la clase. Por ejemplo, si la clase se hizo disponible en la clase SomeData en el campo global:

>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')

La firma completa es:

Enum(value='NewEnumName', names=<...>, *, module='...', qualname='...', type=<mixed-in class>, start=1)
valor

Lo que la nueva clase Enum registrará como su nombre.

nombres

Los miembros de Enum. Esto puede ser un espacio en blanco o una cadena separada por comas (los valores empezarán en 1 a menos que se especifique lo contrario):

'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'

o un iterador de nombres:

['RED', 'GREEN', 'BLUE']

o un iterador de pares(nombre,valor):

[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]

o un mapeo:

{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
módulo

nombre del módulo donde se puede encontrar la nueva clase Enum.

qualname

donde en el módulo se puede encontrar la nueva clase Enum.

tipo

escriba para mezclar en la nueva clase Enum.

inicio

número para comenzar a contar sí solo se pasan nombres.

Distinto en la versión 3.5: Se agregó el parámetro start.

Enumeraciones derivadas

IntEnum

La primera variación de Enum que se proporciona también es una subclase de int. Los miembros de IntEnum se pueden comparar con enteros; por extensión, las enumeraciones enteras de diferentes tipos también se pueden comparar entre sí:

>>> from enum import IntEnum
>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Request(IntEnum):
...     POST = 1
...     GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True

Sin embargo, todavía no se pueden comparar con las enumeraciones estándar Enum:

>>> class Shape(IntEnum):
...     CIRCLE = 1
...     SQUARE = 2
...
>>> class Color(Enum):
...     RED = 1
...     GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False

los valores IntEnum se comportan como enteros en otras maneras que esperarías:

>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]

IntFlag

La siguiente variación de Enum proporcionada, IntFlag, también se basa en int. La diferencia es que los miembros IntFlag se pueden combinar usando los operadores (&, |, ^, ~) y el resultado es un miembro IntFlag. Sin embargo, como su nombre lo indica, los miembros de IntFlag también son subclase int y pueden usarse siempre que int se use. Cualquier operación en un miembro IntFlag además de las operaciones de bit perderán la membresía IntFlag.

Nuevo en la versión 3.6.

Clase muestra IntFlag:

>>> from enum import IntFlag
>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True

También es posible nombrar las combinaciones:

>>> class Perm(IntFlag):
...     R = 4
...     W = 2
...     X = 1
...     RWX = 7
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm.-8: -8>

Otra diferencia importante entre IntFlag y Enum es que si no hay banderas establecidas (el valor es 0), su evaluación booleana es False:

>>> Perm.R & Perm.X
<Perm.0: 0>
>>> bool(Perm.R & Perm.X)
False

Porque los miembros IntFlag también son subclases de int se pueden combinar con ellos:

>>> Perm.X | 8
<Perm.8|X: 9>

Bandera

La última variación es Flag. Al igual que IntFlag, Flag los miembros se pueden combinar usando los operadores (&, |, ^, ~). A diferencia de IntFlag, no pueden combinar ni comparar con ninguna otra enumeración Flag, ni con int. Es posible especificar los valores directamente, se recomienda usar auto como el valor y dejar que Flag seleccione el valor apropiado.

Nuevo en la versión 3.6.

Al igual que IntFlag, si una combinación de miembros Flag resultan en que no se establezcan banderas, la evaluación booleana es False:

>>> from enum import Flag, auto
>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color.0: 0>
>>> bool(Color.RED & Color.GREEN)
False

Las banderas individuales deben tener valores que sean potencias de dos (1, 2, 4, 8, …), mientras que las combinaciones de banderas no:

>>> class Color(Flag):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...     WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>

Dar un nombre a la condición «sin banderas establecidas» no cambia su valor booleano:

>>> class Color(Flag):
...     BLACK = 0
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False

Nota

Para la mayoría del código nuevo, Enum y Flag son muy recomendables, ya que IntEnum y IntFlag rompen algunas promesas semánticas de una enumeración (al ser comparables con enteros, y por transitividad a otras enumeraciones no relacionadas). IntEnum y IntFlag deben usarse solo en casos donde Enum y Flag no son suficientes: por ejemplo, cuando las constantes enteras se reemplazan por enumeraciones, o por interoperabilidad con otros sistemas.

Otros

Mientras que IntEnum es parte del módulo enum, sería muy simple de implementar de forma independiente:

class IntEnum(int, Enum):
    pass

Esto demuestra que similares pueden ser las enumeraciones derivadas; por ejemplo una StrEnum que se mezcla en str en lugar de int.

Algunas reglas:

  1. Al subclasificar Enum, los tipos mixtos deben aparecer antes Enum en la secuencia de bases, como en el ejemplo anterior IntEnum.

  2. Mientras que Enum puede tener miembros de cualquier tipo, una vez que se mezcle tipos adicionales, todos los miembros deben de tener los valores de ese tipo, por ejemplo, int de arriba. Esta restricción no se aplica a las mezclas que solo agregan métodos y no especifican otro tipo.

  3. Cuando se mezcla otro tipo de datos, el atributo value no es el mismo que el mismo miembro enum, aunque es equivalente y se comparará igual.

  4. Formato %-style: %s y %r llaman, respectivamente, a __str__() y __repr__() de la clase Enum; otros códigos (como &i o %h para IntEnum) tratan al miembro enum como su tipo mixto.

  5. Cadenas de caracteres literales formateadas, str.format(), y format() usará el tipo mixto __format__() a menos que __str__() o __format__() se sobreescriba en la subclase, en cuyo caso se utilizarán los métodos anulados o Enum. Use los códigos de formato !s y !r para forzar el uso de los métodos Enum de las clases __str__() y __repr__().

Cuándo usar __new__() contra __init__()

__new__() debe usarse siempre que desee personalizar el valor del miembro real Emum. Cualquier otra modificación puede ir en __new__() o __init__(), prefiriendo siempre __init__().

Por ejemplo, si desea pasar varios elementos al constructor, pero solo desea que uno de ellos sea el valor:

>>> class Coordinate(bytes, Enum):
...     """
...     Coordinate with binary codes that can be indexed by the int code.
...     """
...     def __new__(cls, value, label, unit):
...         obj = bytes.__new__(cls, [value])
...         obj._value_ = value
...         obj.label = label
...         obj.unit = unit
...         return obj
...     PX = (0, 'P.X', 'km')
...     PY = (1, 'P.Y', 'km')
...     VX = (2, 'V.X', 'km/s')
...     VY = (3, 'V.Y', 'km/s')
...

>>> print(Coordinate['PY'])
Coordinate.PY

>>> print(Coordinate(3))
Coordinate.VY

Ejemplos interesantes

Si bien se espera que Enum, IntEnum, IntFlag, y Flag cubran la mayoría de los casos de uso, no pueden cubrirlos a todos. Aquí hay recetas para algunos tipos diferentes de enumeraciones que puede usarse directamente, o como ejemplos para crear los propios.

Omitir valores

En muchos casos de uso, a uno no le importa cuál es el valor real de una enumeración. Hay varias formas de definir este tipo de enumeración simple:

  • use instancias de auto para el valor

  • use instancias de object como el valor

  • use a descriptive string as the value

  • use una tupla como valor y un __new__() personalizado para reemplazar la tupla con un valor int

El uso de cualquiera de estos métodos significa para el usuario que estos valores no son importantes y también permite agregar, eliminar o reordenar miembros sin tener que volver a numerar los miembros restantes.

Cualquiera que sea el método que elijas, debe proporcionar un repr() que también oculte el valor (sin importancia):

>>> class NoValue(Enum):
...     def __repr__(self):
...         return '<%s.%s>' % (self.__class__.__name__, self.name)
...

Usando auto

Usando auto se vería como:

>>> class Color(NoValue):
...     RED = auto()
...     BLUE = auto()
...     GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN>

Usando object

Usando object se vería como:

>>> class Color(NoValue):
...     RED = object()
...     GREEN = object()
...     BLUE = object()
...
>>> Color.GREEN
<Color.GREEN>

Usando una cadena descriptiva

Usar una cadena como valor se vería así:

>>> class Color(NoValue):
...     RED = 'stop'
...     GREEN = 'go'
...     BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
'go'

Usando __new__() personalizados

Usando una numeración automática __new__() se vería como:

>>> class AutoNumber(NoValue):
...     def __new__(cls):
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...
>>> class Color(AutoNumber):
...     RED = ()
...     GREEN = ()
...     BLUE = ()
...
>>> Color.GREEN
<Color.GREEN>
>>> Color.GREEN.value
2

Para hacer un AutoNumber de propósito más general, agregue *args a la firma:

>>> class AutoNumber(NoValue):
...     def __new__(cls, *args):      # this is the only change from above
...         value = len(cls.__members__) + 1
...         obj = object.__new__(cls)
...         obj._value_ = value
...         return obj
...

Luego, cuando hereda de AutoNumber, puede escribir su propio __init__ para manejar cualquier argumento adicional:

>>> class Swatch(AutoNumber):
...     def __init__(self, pantone='unknown'):
...         self.pantone = pantone
...     AUBURN = '3497'
...     SEA_GREEN = '1246'
...     BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'

Nota

El método __new__(), está definido, se usa durante la creación de los miembros Enum; se remplaza entonces por el Enum __new__() que se utiliza después de la creación de la clase para buscar miembros existentes.

OrderedEnum

Una enumeración ordenada que no se basa en IntEnum y, por lo tanto mantiene los invariantes normales de Enum (como no ser comparables con otras enumeraciones):

>>> class OrderedEnum(Enum):
...     def __ge__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value >= other.value
...         return NotImplemented
...     def __gt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value > other.value
...         return NotImplemented
...     def __le__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value <= other.value
...         return NotImplemented
...     def __lt__(self, other):
...         if self.__class__ is other.__class__:
...             return self.value < other.value
...         return NotImplemented
...
>>> class Grade(OrderedEnum):
...     A = 5
...     B = 4
...     C = 3
...     D = 2
...     F = 1
...
>>> Grade.C < Grade.A
True

DuplicateFreeEnum

Levanta un error si se encuentra un nombre de miembro duplicado en lugar de crear un alias:

>>> class DuplicateFreeEnum(Enum):
...     def __init__(self, *args):
...         cls = self.__class__
...         if any(self.value == e.value for e in cls):
...             a = self.name
...             e = cls(self.value).name
...             raise ValueError(
...                 "aliases not allowed in DuplicateFreeEnum:  %r --> %r"
...                 % (a, e))
...
>>> class Color(DuplicateFreeEnum):
...     RED = 1
...     GREEN = 2
...     BLUE = 3
...     GRENE = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum:  'GRENE' --> 'GREEN'

Nota

Este es un ejemplo útil para subclasificar Enum para agregar o cambiar otros comportamientos, así como no permitir alias. Si el único cambio deseado es no permitir alias, el decorador unique() puede usarse en su lugar.

Planeta

Si __new__() o __init__() se definen el valor del miembro enum se pasará a estos métodos:

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     VENUS   = (4.869e+24, 6.0518e6)
...     EARTH   = (5.976e+24, 6.37814e6)
...     MARS    = (6.421e+23, 3.3972e6)
...     JUPITER = (1.9e+27,   7.1492e7)
...     SATURN  = (5.688e+26, 6.0268e7)
...     URANUS  = (8.686e+25, 2.5559e7)
...     NEPTUNE = (1.024e+26, 2.4746e7)
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

Periodo de tiempo

Un ejemplo para mostrar el atributo ignore en uso:

>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
...     "different lengths of time"
...     _ignore_ = 'Period i'
...     Period = vars()
...     for i in range(367):
...         Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]

¿Cómo son diferentes las Enums?

Los Enums tienen una metaclase personalizada que afecta muchos aspectos, tanto de las clases derivadas Enum como de sus instancias (miembros).

Clases Enum

La meta clase EnumMeta es responsable de proveer los métodos __contains__(), __dir__(), __iter__() y cualquier otro que permita hacer cosas con una clase Enum que falla en una clase típica, como list(Color) o some_enum_var in Color. EnumMeta es responsable de asegurar que los otro varios métodos en la clase final Enum sean correctos (como __new__(), __getnewargs__(), __str__() y __repr__()).

Miembros de Enum (también conocidos como instancias)

Lo más interesante de los miembros de Enum es que son únicos. Enum los crea todos mientras está creando la clase Enum misma, y después un __new__() personalizado para garantizar que nunca se creen instancias nuevas retornando solo las instancias de miembros existentes.

Puntos más finos

Nombres soportados __dunder__

__members__ es una asignación ordenada de solo lectura de artículos member_name:member. Solo está disponible en la clase.

__new__(), si se especifica, debe crear y retornar los miembros de enumeración; también es una muy buena idea establecer el _value_ del miembro apropiadamente. Una vez que se crean todos los miembros, ya no se usa.

Nombres _sunder_ compatibles

  • _name_— nombre del miembro

  • _value_ — valor del miembro; se puede definir / modificar en __new__

  • _missing_ — una función de búsqueda utilizada cuando no se encuentra un valor; puede ser anulado

  • _ignore_ – una lista de nombres, ya sea como una list() o una str() que no será transformada en miembros, y que se eliminará de la clase final

  • _order_ — usado en código Python 2/3 para asegurar que el orden de los miembros sea consistente (atributo de clase, eliminado durante la creación de la clase)

  • _generate_next_value_ — usado por la Funcional API y por auto para obtener un valor apropiado para un miembro enum; puede ser anulado

Nuevo en la versión 3.6: _missing_, _order_, _generate_next_value_

Nuevo en la versión 3.7: _ignore_

Para ayudar a mantener sincronizado el código Python 2 / Python 3 se puede proporcionar un atributo _order_. Se verificará con el orden real de la enumeración y generará un error si los dos no coinciden:

>>> class Color(Enum):
...     _order_ = 'RED GREEN BLUE'
...     RED = 1
...     BLUE = 3
...     GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_

Nota

En código Python 2 el atributo _order_ es necesario ya que el orden de definición se pierde antes de que se pueda registrar.

_Private__names

Private names will be normal attributes in Python 3.11 instead of either an error or a member (depending on if the name ends with an underscore). Using these names in 3.9 and 3.10 will issue a DeprecationWarning.

Tipo de miembro Enum

Los miembros Enum son instancias de su clase Enum, y normalmente se accede a ellos como EnumClass.member. Bajo ciertas circunstancias también se puede acceder como EnumClass.member.member, pero nunca se debe hacer esto ya que esa búsqueda puede fallar, o peor aún, retornar algo además del miembro Enum que está buscando (esta es otra buena razón para usar solo mayúsculas en los nombres para los miembros):

>>> class FieldTypes(Enum):
...     name = 0
...     value = 1
...     size = 2
...
>>> FieldTypes.value.size
<FieldTypes.size: 2>
>>> FieldTypes.size.value
2

Nota

This behavior is deprecated and will be removed in 3.11.

Distinto en la versión 3.5.

Valor booleano de las clases y miembros Enum

Lo miembros Enum que están mezclados con tipos sin-Enum (como int, str, etc.) se evalúan de acuerdo con las reglas de tipo mixto; de lo contrario, todos los miembros evalúan como True. Para hacer que tu propia evaluación booleana de Enum dependa del valor del miembro, agregue lo siguiente a su clase:

def __bool__(self):
    return bool(self.value)

las clases Enum siempre evalúan como True.

Enum clases con métodos

Si le da a su subclase Enum métodos adicionales, como la clase Planet anterior, esos métodos aparecerán en una dir() del miembro, pero no de la clase

>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value']

Combinando miembros de``Flag``

Si no se nombra una combinación de miembros de Flag, el repr() incluirá todos los flags con nombre y todas las combinaciones de flags con nombre que estén en el valor

>>> class Color(Flag):
...     RED = auto()
...     GREEN = auto()
...     BLUE = auto()
...     MAGENTA = RED | BLUE
...     YELLOW = RED | GREEN
...     CYAN = GREEN | BLUE
...
>>> Color(3)  # named combination
<Color.YELLOW: 3>
>>> Color(7)      # not named combination
<Color.CYAN|MAGENTA|BLUE|YELLOW|GREEN|RED: 7>

Nota

In 3.11 unnamed combinations of flags will only produce the canonical flag members (aka single-value flags). So Color(7) would produce something like <Color.BLUE|GREEN|RED: 7>.