Python 3.4引入了一个新模块enum
,它为该语言添加了enumerated type。 enum.Enum
的文档提供了an example来演示如何扩展它:
>>> 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
此示例还演示了Enum
的问题:在surface_gravity()
属性方法中,定义了一个常量G
,通常在类级别定义 - 但尝试在内部执行此操作Enum
只是将其添加为枚举的一个成员,因此它已在方法内定义。
如果该类想在其他方法中使用这个常量,那么它也必须在那里定义,这显然不太理想。
有没有办法在Enum
内定义一个类常量,或者一些解决方法来达到同样的效果?
答案 0 :(得分:43)
这是高级行为,在90%以上的枚举中不需要。
根据文件:
允许的规则如下:
_sunder_
名称(以单个下划线开头和结尾)由enum保留,不能使用;枚举中定义的所有其他属性将成为此枚举的成员,但__dunder__
名称和descriptors
除外(方法也是描述符)。
因此,如果你想要一个类常数,你有几个选择:
__init__
descriptor
在__init__
中创建常量并在创建类之后添加常量,但都不会在一个地方收集所有类信息。
当然可以在适当的时候使用Mixins(see dnozay's answer for a good example),但是通过使用内置实际常量的基类Enum
类也可以简化这种情况。
首先,将在下面的示例中使用的常量:
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)
一次性使用Enum示例:
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
最后,多用途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
注意强>:
Constant
G
确实不是。可以将G
重新绑定到其他内容:
Planet.G = 1
如果你真的需要它是常量(也就是不可重新绑定),那么使用new aenum library [1]将阻止尝试重新分配constant
和Enum
成员。
1 披露:我是Python stdlib Enum
,enum34
backport和Advanced Enumeration (aenum
)图书馆的作者。
答案 1 :(得分:13)
最优雅的解决方案(IMHO)是使用mixins / base class来提供正确的行为。
Satellite
和Planet
。Satellite
和Planet
可能必须提供不同的行为)以下是您首先定义行为的示例:
#
# 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)
然后使用正确的基类/ mixins创建Enum
。
#
# 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)
答案 2 :(得分:9)
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)
@property无法工作且classconstant能够工作的原因非常简单,并在answer here
中进行了解释访问时返回实际属性对象的原因 它通过类Hello.foo位于属性如何实现
__get__(self, instance, owner)
特殊方法。如果在实例上访问描述符,那么该实例将作为 适当的参数,所有者是该实例的类。另一方面,如果通过类访问它,那么实例 是无,只有所有者通过。 属性对象识别出这一点 并返回自我。
因此,classproperty
中的代码实际上是property
的一般化,缺少if instance is None
部分。
答案 3 :(得分:4)
property
可用于提供类常量的大部分行为:
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)
如果你想定义大量的常量,这会有点笨拙,所以你可以在类之外定义一个辅助函数:
def constant(c):
"""Return a class property that returns `c`."""
return property(lambda self: c)
...并按如下方式使用:
class Planet(Enum):
# ...
G = constant(6.67300E-11)
这种方法的一个限制是它只适用于类的实例,而不适用于类本身:
>>> Planet.EARTH.G
6.673e-11
>>> Planet.G
<property object at 0x7f665921ce58>
答案 4 :(得分:-1)
TLDR;不,它不能在Enum类中完成。
这说,正如其他答案所示,有一些方法可以获得与Enum相关联的类所拥有的值(即通过类继承/ mixins),但这些值不是“在Enum中定义的”。