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 ydescriptors
__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
yPlanet
. - mixins son interesantes si decides proporcionar un comportamiento opcional (por ejemplo,
Satellite
yPlanet
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.