Enumeración basada en cadenas en Python

7 minutos de lectura

avatar de usuario de sophros
sofros

Para encapsular una lista de estados que estoy usando enum módulo:

from enum import Enum

class MyEnum(Enum):
    state1='state1'
    state2 = 'state2'

state = MyEnum.state1
MyEnum['state1'] == state  # here it works
'state1' == state  # here it does not throw but returns False (fail!)

Sin embargo, el problema es que necesito usar sin problemas los valores como cadenas en muchos contextos en mi secuencia de comandos, como:

select_query1 = select(...).where(Process.status == str(MyEnum.state1))  # works but ugly

select_query2 = select(...).where(Process.status == MyEnum.state1)  # throws exeption

Cómo hacerlo evitando llamar a conversión de tipo adicional (str(state) arriba) o el valor subyacente (state.value)?

  • MyEnum.state1.value?

    – Dan

    29 oct 2019 a las 13:30

  • Lo siento pero esto es igual de feo que str(state) a mi…

    – sophros

    29 oct 2019 a las 13:40

  • que tipo es Testround.status? ¿Podrías hacerlo de tipo MyEnum?

    – Dan

    29 oct 2019 a las 14:25

  • Su código de ejemplo 'state1' == state está mal, esa comparación regresa False.

    –Ethan Furman

    29 oct 2019 a las 14:39

  • @EthanFurman: no arroja, pero de hecho el resultado está lejos de ser satisfactorio. ¡Gracias! De corrección.

    – sophros

    29 oct 2019 a las 15:03

avatar de usuario de sophros
sofros

Parece que basta con heredar de str clase al mismo tiempo que Enum:

from enum import Enum

class MyEnum(str, Enum):
    state1 = 'state1'
    state2 = 'state2'

La parte complicada es que el orden de clases en la cadena de herencia es importante como esto:

class MyEnum(Enum, str):
    state1 = 'state1'
    state2 = 'state2'

lanza:

TypeError: new enumerations should be created as `EnumName([mixin_type, ...] [data_type,] enum_type)`

Con la clase correcta las siguientes operaciones en MyEnum estan bien:

print('This is the state value: ' + state)

Como nota al margen, parece que el truco de herencia especial no es necesario para cadenas formateadas que funcionan incluso para Enum solo herencia:

msg = f'This is the state value: {state}'  # works without inheriting from str

  • Documentos relevantes: docs.python.org/3/library/enum.html#restricted-enum-subclassing

    – Aran Fey

    29 oct 2019 a las 13:34

  • Tenga en cuenta que aa str mixin puede tener un efecto secundario no deseado, por ejemplo, en el comportamiento de (des) serialización; consulte, por ejemplo, stackoverflow.com/q/65339635/10009545

    – koks der drache

    17 de diciembre de 2020 a las 12:55

Avatar de usuario de Elyasaf755
elyasaf755

Al leer la documentación (es decir, no lo probé porque uso una versión anterior de Python, pero confío en los documentos), ya que Pitón 3.11 puedes hacer lo siguiente:

from enum import StrEnum

class Directions(StrEnum):
    NORTH = 'north',    # notice the trailing comma
    SOUTH = 'south'

print(Directions.NORTH)
>>> north

por favor refiérase a documentos y el discusión de diseño para una mayor comprensión.

si estas corriendo pitón 3.6+ejecutar pip install StrEnumy luego puedes hacer lo siguiente (confirmado por mí):

from strenum import StrEnum

class URLs(StrEnum):
    GOOGLE = 'www.google.com'
    STACKOVERFLOW = 'www.stackoverflow.com'

print(URLs.STACKOVERFLOW)

>>> www.stackoverflow.com

Puedes leer más al respecto aquí.

Además, esto se mencionó en los documentos: cómo crear sus propias enumeraciones basadas en otras clases:

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

class IntEnum(int, Enum): pass Esto demuestra cómo se pueden definir enumeraciones derivadas similares; por ejemplo, un StrEnum que se mezcla en str en lugar de int.

Algunas reglas:

Al subclasificar Enum, los tipos de combinación deben aparecer antes que Enum en la secuencia de bases, como en el ejemplo anterior de IntEnum.

Si bien Enum puede tener miembros de cualquier tipo, una vez que mezcle un tipo adicional, todos los miembros deben tener valores de ese tipo, por ejemplo, int arriba. Esta restricción no se aplica a los complementos que solo agregan métodos y no especifican otro tipo.

Cuando se mezcla otro tipo de datos, el atributo de valor no es el mismo que el propio miembro de la enumeración, aunque es equivalente y se comparará igual.

Formato de estilo %: %s y %r llaman a la clase Enum calle() y
repetir() respectivamente; otros códigos (como %i o %h para IntEnum) tratan el miembro de enumeración como su tipo mixto.

Los literales de cadena formateados, str.format() y format() utilizarán los tipos combinados formato() a no ser que calle() o formato() se anula en la subclase, en cuyo caso se utilizarán los métodos anulados o los métodos Enum. Use los códigos de formato !s y !r para forzar el uso de la clase Enum calle() y repetir() métodos.

Fuente: https://docs.python.org/3/library/enum.html#others

  • es 3.11 para StrEnum

    – Rohit

    17 dic 2021 a las 16:41

  • Usemos valores de enumeración generados automáticamente en la serialización. Entonces podría romperse fácilmente actualizando a la versión con errores ‘strenum’. Que no vale la pena.

    – Denis Barmenkov

    8 de febrero a las 0:07


  • Do. No funciona en 3.7.3 🙁

    – Mota Zart

    22 abr a las 23:09

Mientras que una clase mixin entre str y Enum puede resolver este problema, siempre debe pensar también en obtener el herramienta adecuada para el trabajo.

Y, a veces, la herramienta adecuada podría ser simplemente un MODULE_CONSTANT con un valor de cadena. Por ejemplo, logging tiene algunas constantes como DEBUG, INFO, etc. con valores significativos, incluso si son ints en este caso.

Las enumeraciones son una buena herramienta y las uso a menudo. Sin embargo, están destinados a compararse principalmente con otros miembros del mismo Enum, razón por la cual compararlos, por ejemplo, con cadenas requiere que salte a través de un aro adicional.

  • Enum se creó para que no se necesitaran constantes de módulos opacos.

    –Ethan Furman

    29 oct 2019 a las 14:36

  • @EthanFurman Entonces, ¿por qué logging.DEBUG y sus amigos no están en desuso?

    – ojo brillante

    29 oct 2019 a las 14:38


  • No quedarían en desuso, sino que se reemplazarían por un correspondiente IntEnum. Es política estándar mantener la stdlib lo más estable posible, lo que significa no reescribirla al por mayor para aprovechar cada característica nueva. Hasta aquí http, sockety re las constantes del módulo han sido reemplazadas (y tal vez un par más que no recuerdo en este momento).

    –Ethan Furman

    29 de octubre de 2019 a las 14:48

  • La parte relevante de PEP 435.

    –Ethan Furman

    29 oct 2019 a las 14:49


  • ¡Vaya, parece que ha pasado mucho tiempo desde que tuvimos esa conversación! Los puntos principales allí se relacionan con mi comentario anterior: no reescribir el stdlib sin una buena razón. El caso de las cuerdas mágicas es aún más difícil de hacer, porque las cuerdas generalmente se explican por sí mismas. Los módulos con más posibilidades de tener Enum las conversiones están orientadas al usuario con constantes enteras (como re y http). Además, una gran razón para no convertir partes específicas de stdlib es si se usó antes Enum se puede importar Todas estas son razones que no afectan el código fuera de stdlib.

    –Ethan Furman

    29 de octubre de 2019 a las 15:28

Avatar de usuario de Shital Shah
sha de mierda

Si los valores de cadena asociados son nombres de Python válidos, puede obtener los nombres de los miembros de la enumeración usando .name propiedad como esta:

from enum import Enum
class MyEnum(Enum):
    state1=0
    state2=1

print (MyEnum.state1.name)  # 'state1'

a = MyEnum.state1
print(a.name)  # 'state1'

Si los valores de cadena asociados son cadenas arbitrarias, puede hacer esto:

class ModelNames(str, Enum):
    gpt2 = 'gpt2'
    distilgpt2 = 'distilgpt2'
    gpt2_xl="gpt2-XL"
    gpt2_large="gpt2-large"

print(ModelNames.gpt2) # 'ModelNames.gpt2'
print(ModelNames.gpt2 is str) # False
print(ModelNames.gpt2_xl.name) # 'gpt2_xl'
print(ModelNames.gpt2_xl.value) # 'gpt2-XL'

Prueba esto en línea: https://repl.it/@sytelus/enumstrtest

Si desea trabajar con cadenas directamente, podría considerar usar

MyEnum = collections.namedtuple(
    "MyEnum", ["state1", "state2"]
)(
    state1="state1", 
    state2="state2"
)

en lugar de enumerar en absoluto. Iterando sobre esto o haciendo MyEnum.state1 dará los valores de cadena directamente. Crear la tupla con nombre dentro de la misma declaración significa que solo puede haber una.

Obviamente, hay ventajas y desventajas por no usar Enum, por lo que depende de lo que valore más.

Avatar de usuario de Gabriele Iannetti
Gabriele Iannetti

¿Qué hay de malo en usar el valor?

En mi humilde opinión, a menos que use Python versión 3.11 con StrEnum, simplemente anulo el __str__(self) método en la clase Enum adecuada:

class MyStrEnum(str, Enum):

    OK     = 'OK'
    FAILED = 'FAILED'

    def __str__(self) -> str:
        return self.value

Mejor

avatar de usuario de pierre-vr
pierre-vr

Simplemente use .value :

MyEnum.state1.value == 'state1'
# True

  • La pregunta establece explícitamente que llamar .value no es una solución aceptable.

    – Encantador de serpientes

    14 de enero de 2021 a las 17:08

¿Ha sido útil esta solución?