是否可以在枚举中定义类常量?

新手上路,请多包涵

Python 3.4 引入了一个新模块 enum ,它为该语言添加了一个 枚举类型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 中这样做只会将其添加为枚举的成员之一,因此它是在方法内部定义的。

如果这个类想在其他方法中使用这个常量,它也必须在那里定义,这显然不是理想的。

有没有办法在 Enum 中定义一个类常量,或者一些解决方法来达到同样的效果?

原文由 Zero Piraeus 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 501
2 个回答

这是高级行为,在创建的 90% 以上的枚举中不需要。

根据文档:

允许的规则如下: _sunder_ 名称(以单个下划线开头和结尾)由枚举保留,不能使用;枚举中定义的所有其他属性将成为该枚举的成员,除了 __dunder__ 名称和 descriptors (方法也是描述符)。

所以如果你想要一个类常量,你有几个选择:

  • __init__ 中创建它
  • 创建类后添加
  • 使用混合
  • 创建你自己的 descriptor

__init__ 中创建常量并在创建类之后添加它,这两种情况都没有将所有类信息收集在一个地方。

Mixins 当然可以在适当的时候使用( 请参阅 dnozay 的回答以获得一个很好的例子),但这种情况也可以通过具有内置实际常量的基础 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

最后,多用途枚举示例:

 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 成员的尝试。


1披露:我是 Python stdlib Enumenum34 backport高级枚举( aenum 库) 的作者

原文由 Ethan Furman 发布,翻译遵循 CC BY-SA 4.0 许可协议

最优雅的解决方案(恕我直言)是使用混合/基类来提供正确的行为。

  • 基类提供所有实现所需的行为,例如 SatellitePlanet
  • 如果您决定提供可选行为, mixins 会很有趣(例如 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)

然后使用正确的基类/mixin 创建您的 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)

原文由 dnozay 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题