enum – Tipo de Enumeración

El módulo enum define un tipo de enumeración con capacidades de iteración y comparación. Puede ser usado para creae símbolos bien definidos para valores, en lugar de usar enteros literales o cadenas.

Creando enumeraciones

Una nueva enumeración se define utilizando la sintaxis class creando una subclase de Enum y agregando atributos de clase que describen los valores.

enum_create.py
import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


print('\nMember name: {}'.format(BugStatus.wont_fix.name))
print('Member value: {}'.format(BugStatus.wont_fix.value))

Los miembros del Enum se convierten en instancias cuando la clase es analizada. Cada instancia tiene una propiedad name que correspondiente al nombre del miembro y una propiedad value correspondiente al valorasignado al nombre en la definición de la clase.

$ python3 enum_create.py

Member name: wont_fix
Member value: 4

Iteración

Iterar sobre la clase emun produce los miembros individuales de la enumeración.

enum_iterate.py
import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

Los miembros se producen en el orden en que se declaran en la definición de la clase. Los nombres y valores no se usan para ordenarlos de ninguna manera.

$ python3 enum_iterate.py

new             = 7
incomplete      = 6
invalid         = 5
wont_fix        = 4
in_progress     = 3
fix_committed   = 2
fix_released    = 1

Comparando Enums

Como los miembros de la enumeración no están ordenados, solo admiten comparación por identidad e igualdad.

enum_comparison.py
import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


actual_state = BugStatus.wont_fix
desired_state = BugStatus.fix_released

print('Equality:',
      actual_state == desired_state,
      actual_state == BugStatus.wont_fix)
print('Identity:',
      actual_state is desired_state,
      actual_state is BugStatus.wont_fix)
print('Ordered by value:')
try:
    print('\n'.join('  ' + s.name for s in sorted(BugStatus)))
except TypeError as err:
    print('  Cannot sort: {}'.format(err))

Los operadores de comparación mayor que y menor que elevan excepciones TypeError.

$ python3 enum_comparison.py

Equality: False True
Identity: False True
Ordered by value:
  Cannot sort: unorderable types: BugStatus() < BugStatus()

Usa la clase IntEnum para enumeraciones donde los miembros necesitan comportarse más como números — por ejemplo, para ofrecer comparaciones.

enum_intenum.py
import enum


class BugStatus(enum.IntEnum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1


print('Ordered by value:')
print('\n'.join('  ' + s.name for s in sorted(BugStatus)))
$ python3 enum_intenum.py

Ordered by value:
  fix_released
  fix_committed
  in_progress
  wont_fix
  invalid
  incomplete
  new

Valore de enumeración únicos

Los miembros de Enum con el mismo valor se tratan como referencias de alias al mismo objeto miembro. Los alias no hacen que los valores repetidos esten presentes en el iterador para el Enum.

enum_aliases.py
import enum


class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    by_design = 4
    closed = 1


for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

print('\nSame: by_design is wont_fix: ',
      BugStatus.by_design is BugStatus.wont_fix)
print('Same: closed is fix_released: ',
      BugStatus.closed is BugStatus.fix_released)

Porque by_design y closed son alias para otros miembros, no aparecen por separado en el resultado al iterar sobre el Enum. El nombre canónico para un miembro es el primer nombre unido al valor.

$ python3 enum_aliases.py

new             = 7
incomplete      = 6
invalid         = 5
wont_fix        = 4
in_progress     = 3
fix_committed   = 2
fix_released    = 1

Same: by_design is wont_fix:  True
Same: closed is fix_released:  True

Para requerir que todos los miembros tengan valores únicos, agregue el decorador @unique al Enum.

enum_unique_enforce.py
import enum


@enum.unique
class BugStatus(enum.Enum):

    new = 7
    incomplete = 6
    invalid = 5
    wont_fix = 4
    in_progress = 3
    fix_committed = 2
    fix_released = 1

    # This will trigger an error with unique applied.
    by_design = 4
    closed = 1

Los miembros con valores repetidos elevan una excepción ValueError cuando la clase Enum está siendo interpretada.

$ python3 enum_unique_enforce.py

Traceback (most recent call last):
  File "enum_unique_enforce.py", line 11, in <module>
    class BugStatus(enum.Enum):
  File ".../lib/python3.5/enum.py", line 573, in unique
    (enumeration, alias_details))
ValueError: duplicate values found in <enum 'BugStatus'>:
by_design -> wont_fix, closed -> fix_released

Creando enumeraciones programáticamente

En algunos casos, es más conveniente crear enumeraciones programáticamente, en lugar de codificarlos en una definició de nclase. Para esas situaciones, Enum también admite pasar los nombres y valores de los miembros al constructor de la clase.

enum_programmatic_create.py
import enum


BugStatus = enum.Enum(
    value='BugStatus',
    names=('fix_released fix_committed in_progress '
           'wont_fix invalid incomplete new'),
)

print('Member: {}'.format(BugStatus.new))

print('\nAll members:')
for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

El argumento value es el nombre de la enumeración, que se usa para compilar la representación de los miembros. El argumento names lista los miembros de la enumeración. Cuando se pasa una sola cadena, es dividida en espacios en blanco y comas, y los tokens resultantes se utilizan como nombres para los miembros, que son valores asignados automáticamente comenzando con 1.

$ python3 enum_programmatic_create.py

Member: BugStatus.new

All members:
fix_released    = 1
fix_committed   = 2
in_progress     = 3
wont_fix        = 4
invalid         = 5
incomplete      = 6
new             = 7

Para un mayor control sobre los valores asociados con los miembros, la cadena names se puede reemplazar por una secuencia de tuplas de dos partes o un diccionario mapeando nombres a valores.

enum_programmatic_mapping.py
import enum


BugStatus = enum.Enum(
    value='BugStatus',
    names=[
        ('new', 7),
        ('incomplete', 6),
        ('invalid', 5),
        ('wont_fix', 4),
        ('in_progress', 3),
        ('fix_committed', 2),
        ('fix_released', 1),
    ],
)

print('All members:')
for status in BugStatus:
    print('{:15} = {}'.format(status.name, status.value))

En este ejemplo, se proporciona una lista de tuplas de dos partes en lugar de una sola cadena que contiene solo los nombres de los miembros. Esto hace posible reconstruir la enumeración BugStatus con los miembros en el mismo orden como la versión definida en enum_create.py.

$ python3 enum_programmatic_mapping.py

All members:
new             = 7
incomplete      = 6
invalid         = 5
wont_fix        = 4
in_progress     = 3
fix_committed   = 2
fix_released    = 1

Valores miembros no enteros

Los valores de miembro de Enum no están restringidos a enteros. De hecho, cualquier tipo de objeto se puede asociar con un miembro. Si el valor es una tupla, los miembros se pasan como argumentos individuales a __init __().

enum_tuple_values.py
import enum


class BugStatus(enum.Enum):

    new = (7, ['incomplete',
               'invalid',
               'wont_fix',
               'in_progress'])
    incomplete = (6, ['new', 'wont_fix'])
    invalid = (5, ['new'])
    wont_fix = (4, ['new'])
    in_progress = (3, ['new', 'fix_committed'])
    fix_committed = (2, ['in_progress', 'fix_released'])
    fix_released = (1, ['new'])

    def __init__(self, num, transitions):
        self.num = num
        self.transitions = transitions

    def can_transition(self, new_state):
        return new_state.name in self.transitions


print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:',
      BugStatus.in_progress.can_transition(BugStatus.new))

En este ejemplo, cada valor de miembro es una tupla que contiene el ID numérico (tal como podría estar almacenado en una base de datos) y una lista de transiciones a partir del estado actual.

$ python3 enum_tuple_values.py

Name: BugStatus.in_progress
Value: (3, ['new', 'fix_committed'])
Custom attribute: ['new', 'fix_committed']
Using attribute: True

Para casos más complejos, las tuplas pueden volverse difíciles de manejar. Como cualquier tipo de objeto puede ser valor miembro, los diccionarios se pueden usar para los casos donde hay muchos atributos separados para seguir por cada valor enum. Los valores complejos se pasan directamente a __init__() como único argumento que no sea self.

enum_complex_values.py
import enum


class BugStatus(enum.Enum):

    new = {
        'num': 7,
        'transitions': [
            'incomplete',
            'invalid',
            'wont_fix',
            'in_progress',
        ],
    }
    incomplete = {
        'num': 6,
        'transitions': ['new', 'wont_fix'],
    }
    invalid = {
        'num': 5,
        'transitions': ['new'],
    }
    wont_fix = {
        'num': 4,
        'transitions': ['new'],
    }
    in_progress = {
        'num': 3,
        'transitions': ['new', 'fix_committed'],
    }
    fix_committed = {
        'num': 2,
        'transitions': ['in_progress', 'fix_released'],
    }
    fix_released = {
        'num': 1,
        'transitions': ['new'],
    }

    def __init__(self, vals):
        self.num = vals['num']
        self.transitions = vals['transitions']

    def can_transition(self, new_state):
        return new_state.name in self.transitions


print('Name:', BugStatus.in_progress)
print('Value:', BugStatus.in_progress.value)
print('Custom attribute:', BugStatus.in_progress.transitions)
print('Using attribute:',
      BugStatus.in_progress.can_transition(BugStatus.new))

Este ejemplo expresa los mismos datos que el ejemplo anterior, utilizando diccionarios en lugar de tuplas.

$ python3 enum_complex_values.py

Name: BugStatus.in_progress
Value: {'transitions': ['new', 'fix_committed'], 'num': 3}
Custom attribute: ['new', 'fix_committed']
Using attribute: True

Ver también