有没有可能在一个枚举中定义一个类常量?

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 /基类提供正确的行为。

  • 提供所有实现所需的行为,例如SatellitePlanet
  • 如果您决定提供可选行为(例如SatellitePlanet可能必须提供不同的行为)

这里是一个例子,你首先定义你的行为:

 # # 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中定义的”。