how define python python-3.x enums constants class-constants

how to define constants in python



¿Es posible definir una constante de clase dentro de un Enum? (5)

Este es un comportamiento avanzado que no será necesario en el 90% de las enumeraciones creadas.

De acuerdo con los documentos:

Las reglas para lo que está permitido son las siguientes: _sunder_ names (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 en una enumeración se convertirán en miembros de esta enumeración, con la excepción de los nombres y descriptors __dunder__ (los métodos también son descriptores).

Entonces, si quieres una clase constante, tienes varias opciones:

  • crearlo en __init__
  • agréguelo después de que se haya creado la clase
  • usa un mixin
  • crea tu propio descriptor

La creación de la constante en __init__ y su adición después de que se haya creado la clase adolecen de no tener toda la información de clase recopilada en un solo lugar.

Mixins ciertamente puede usarse cuando sea apropiado ( vea la respuesta de dnozay para un buen ejemplo ), pero ese caso también se puede simplificar teniendo una clase base Enum con las constantes reales incorporadas.

Primero, la constante que se usará en los ejemplos a continuación:

class Constant: # use Constant(object) if in Python 2 def __init__(self, value): self.value = value def __get__(self, *args): return self.value def __repr__(self): return ''%s(%r)'' % (self.__class__.__name__, self.value)

Y el ejemplo de Enum de un solo uso:

from enum import Enum 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) # universal gravitational constant G = Constant(6.67300E-11) def __init__(self, mass, radius): self.mass = mass # in kilograms self.radius = radius # in meters @property def surface_gravity(self): return self.G * self.mass / (self.radius * self.radius) print(Planet.__dict__[''G'']) # Constant(6.673e-11) print(Planet.G) # 6.673e-11 print(Planet.NEPTUNE.G) # 6.673e-11 print(Planet.SATURN.surface_gravity) # 10.44978014597121

Y, finalmente, el ejemplo multiuso de Enum:

from enum import Enum class AstronomicalObject(Enum): # universal gravitational constant G = Constant(6.67300E-11) def __init__(self, mass, radius): self.mass = mass self.radius = radius @property def surface_gravity(self): return self.G * self.mass / (self.radius * self.radius) class Planet(AstronomicalObject): 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) class Asteroid(AstronomicalObject): CERES = (9.4e+20 , 4.75e+5) PALLAS = (2.068e+20, 2.72e+5) JUNOS = (2.82e+19, 2.29e+5) VESTA = (2.632e+20 ,2.62e+5 Planet.MERCURY.surface_gravity # 3.7030267229659395 Asteroid.CERES.surface_gravity # 0.27801085872576176

Nota :

La Constant G realmente no lo es. Uno podría volver a unir G a algo más:

Planet.G = 1

Si realmente necesita que sea constante (también conocido como no reencadenable), entonces use la nueva biblioteca aenum [1] que bloqueará los intentos de reasignar s tan bien como los miembros de Enum .

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

Python 3.4 introduce una nueva enum módulo, que agrega un tipo enumerado al lenguaje. La documentación de enum.Enum proporciona un ejemplo para demostrar cómo se puede extender:

>>> 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

Este ejemplo también demuestra un problema con Enum : en el método de propiedad surface_gravity() , se define una constante G que normalmente se definiría a nivel de clase, pero intentar hacerlo dentro de un Enum simplemente lo agregaría como uno de los miembros del enum, por lo que en su lugar se ha definido dentro del método.

Si la clase quería usar esta constante en otros métodos, también tendría que definirse allí, lo que obviamente no es ideal.

¿Hay alguna manera de definir una constante de clase dentro de un Enum , o alguna solución alternativa para lograr el mismo efecto?


La solución más elegante (en mi humilde opinión) es usar mixins / clase base para proporcionar el comportamiento correcto.

  • clase base para proporcionar el comportamiento que se necesita para todas las implementaciones que son comunes, por ejemplo, Satellite y Planet .
  • mixins son interesantes si decides proporcionar un comportamiento opcional (por ejemplo, Satellite y Planet pueden tener que proporcionar un comportamiento diferente)

Aquí hay un ejemplo donde primero defines tu comportamiento:

# # business as usual, define your class, methods, constants... # class AstronomicalObject: # universal gravitational constant G = 6.67300E-11 def __init__(self, mass, radius): self.mass = mass # in kilograms self.radius = radius # in meters class PlanetModel(AstronomicalObject): @property def surface_gravity(self): return self.G * self.mass / (self.radius * self.radius) class SatelliteModel(AstronomicalObject): FUEL_PRICE_PER_KG = 20000 @property def fuel_cost(self): return self.FUEL_PRICE_PER_KG * self.mass def falling_rate(self, destination): return complicated_formula(self.G, self.mass, destination)

Luego crea tu Enum con las clases base / mixins correctas.

# # then create your Enum with the correct model. # class Planet(PlanetModel, 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) class Satellite(SatelliteModel, Enum): GPS1 = (12.0, 1.7) GPS2 = (22.0, 1.5)


TLDR; NO, no se puede hacer dentro de una clase Enum.

Dicho esto, como mostraron las otras respuestas, hay formas de obtener los valores de propiedad de clase asociados a un Enum (es decir, a través de la herencia de clases / mixins), pero dichos valores no están "definidos ... dentro de un Enum".


Una property se puede usar para proporcionar la mayor parte del comportamiento de una constante de clase:

class Planet(Enum): # ... @property def G(self): return 6.67300E-11 # ... @property def surface_gravity(self): return self.G * self.mass / (self.radius * self.radius)

Esto sería un poco complicado si quisiera definir un gran número de constantes, por lo que podría definir una función auxiliar fuera de la clase:

def constant(c): """Return a class property that returns `c`.""" return property(lambda self: c)

... y úsalo de la siguiente manera:

class Planet(Enum): # ... G = constant(6.67300E-11)

Una limitación de este enfoque es que solo funcionará para las instancias de la clase, y no para la clase en sí misma:

>>> Planet.EARTH.G 6.673e-11 >>> Planet.G <property object at 0x7f665921ce58>


from enum import Enum class classproperty(object): """A class property decorator""" def __init__(self, getter): self.getter = getter def __get__(self, instance, owner): return self.getter(owner) class classconstant(object): """A constant property from given value, visible in class and instances""" def __init__(self, value): self.value = value def __get__(self, instance, owner): return self.value class strictclassconstant(classconstant): """A constant property that is callable only from the class """ def __get__(self, instance, owner): if instance: raise AttributeError( "Strict class constants are not available in instances") return self.value 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 G = classconstant(6.67300E-11) @property def surface_gravity(self): # universal gravitational constant (m3 kg-1 s-2) return Planet.G * self.mass / (self.radius * self.radius) print(Planet.MERCURY.surface_gravity) print(Planet.G) print(Planet.MERCURY.G) class ConstantExample(Enum): HAM = 1 SPAM = 2 @classproperty def c1(cls): return 1 c2 = classconstant(2) c3 = strictclassconstant(3) print(ConstantExample.c1, ConstantExample.HAM.c1) print(ConstantExample.c2, ConstantExample.SPAM.c2) print(ConstantExample.c3) # This should fail: print(ConstantExample.HAM.c3)

La razón por la cual @property NO funciona y el trabajo con clase DOES es bastante simple, y se explica en la respuesta aquí

La razón por la que se devuelve el objeto de propiedad real cuando se accede a través de una clase Hello.foo reside en cómo la propiedad implementa el método especial __get__(self, instance, owner) . Si se accede a un descriptor en una instancia, dicha instancia se pasa como el argumento apropiado, y el propietario es la clase de esa instancia.

Por otro lado, si se accede a través de la clase, la instancia es Ninguna y solo se pasa el propietario. El objeto de propiedad lo reconoce y regresa a sí mismo.

Por lo tanto, el código en classproperty de classproperty es en realidad una generalización de property , que carece de la if instance is None parte.