有没有可能在一个枚举中定义一个类常量?
Python 3.4引入了一个新的模块enum
,它将枚举types添加到语言中。 enum.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) ... 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
尝试这样做只会将其作为枚举,所以相反,它已被定义在方法内部。
如果class级想用其他方法使用这个常数,那么也必须在那里定义,这显然是不理想的。
有什么办法可以在一个Enum
定义一个类常量,或者一些解决方法来实现相同的效果?
这是高级行为,在所创build的枚举的90%以上将不需要。
根据文件:
允许的规则如下:
_sunder_
名称(以单个下划线开头和结尾)由枚举保留,不能使用; 在枚举中定义的所有其他属性都将成为此枚举的成员,除了__dunder__
名称和descriptors
(方法也是描述符)之外。
所以如果你想要一个类常量,你有几个select:
- 在
__init__
创build它 - 在课程创build后添加它
- 使用混合
- 创build你自己的
descriptor
在__init__
创build常量并在创build类之后添加它们都会遇到没有收集到所有类信息的问题。
Mixins当然可以在适当的时候使用( 请参考dnozay的答案,一个很好的例子 ),但是这种情况也可以通过build立一个实际的常量的基类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)
和一次性使用枚举的例子:
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
如果你真的需要它是恒定的(又名不可重新绑定),那么使用新的aenum库 [1],它将阻止尝试重新分配constant
s以及Enum
成员。
aenum
是由enum34
的作者写的。
最优雅的解决scheme(恕我直言)是使用mixins /基类提供正确的行为。
- 提供所有实现所需的行为,例如
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)
然后用正确的基类/混入创build你的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)
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工作的原因很简单,在这里的答案中解释
当通过类Hello.foo访问实际属性对象时,返回的原因在于属性如何实现
__get__(self, instance, owner)
特殊方法。 如果在实例上访问描述符,那么该实例将作为适当的parameter passing,而owner则是该实例的类。另一方面,如果通过类访问它,那么实例是None,只有所有者被传递。 属性对象识别这个并返回self。
因此, classproperty
的代码实际上是property
的泛化,缺lessif instance is None
部分。
一个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>
TLDR; 不,它不能在Enum类中完成。
这就是说,正如其他答案所显示的,有办法获得与Enum相关的类拥有的值(即通过类inheritance/ mixin),但是这些值不是“在Enum中定义的”。