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
Case of Enum Members
Because Enums are used to represent constants we recommend using UPPER_CASE names for enum members, and will be using that style in our examples.
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.
IntFlag
¶ Clase base para crear constantes enumeradas que se pueden combinar usando los operadores bitwise sin perder su membresía
IntFlag
. Los miembros deIntFlag
también son subclases deint
.
-
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
esROJO
, el valor deColor.BLUE
es3
, 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
The goal of the default _generate_next_value_()
method is to provide
the next int
in sequence with the last int
provided, but
the way it does this is an implementation detail and may change.
Nota
The _generate_next_value_()
method must be defined before any members.
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__()
y/o __init__()
, los valores que se hayan dado al miembro enum se pasarán a esos métodos. Ver Planeta 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:
Al subclasificar
Enum
, los tipos mixtos deben aparecer antesEnum
en la secuencia de bases, como en el ejemplo anteriorIntEnum
.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, ej.int
de arriba. Esta restricción no se aplica a las mezclas que solo agregan métodos y no especifican otro tipo de datos comoint
ostr
.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.Formato %-style: %s y %r llaman, respectivamente, a
__str__()
y__repr__()
de la claseEnum
; otros códigos (como &i o %h para IntEnum) tratan al miembro enum como su tipo mixto.Formatted string literals,
str.format()
, andformat()
will use the mixed-in type’s__format__()
unless__str__()
or__format__()
is overridden in the subclass, in which case the overridden methods orEnum
methods will be used. Use the !s and !r format codes to force usage of theEnum
class’s__str__()
and__repr__()
methods.
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 valoruse instancias de
object
como el valoruse a descriptive string as the value
use una tupla como valor y un
__new__()
personalizado para reemplazar la tupla con un valorint
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
To make a more general purpose AutoNumber
, add *args
to the signature:
>>> 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
...
Then when you inherit from AutoNumber
you can write your own __init__
to handle any extra arguments:
>>> 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'
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 miembr0; 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 unalist()
o unastr()
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 porauto
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.
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
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)
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>