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

Zer*_*eus 50 python enums constants class-constants python-3.x

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
Run Code Online (Sandbox Code Playgroud)

这个例子还演示了一个问题Enum:在surface_gravity()属性方法中,G定义了一个常常在类级别定义的常量- 但是在一个内部尝试这样做Enum只会将它添加为枚举的一个成员,所以相反它就是在方法内部定义.

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

有没有办法在一个内部定义一个类常量Enum,或者一些变通方法来实现相同的效果?

Eth*_*man 44

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

根据文件:

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

因此,如果您想要一个类常量,您有几个选择:

  • 创造它 __init__
  • 在创建类之后添加它
  • 使用mixin
  • 创造你自己的 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)
Run Code Online (Sandbox Code Playgroud)

并且一次性使用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
Run Code Online (Sandbox Code Playgroud)

最后,多用途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
Run Code Online (Sandbox Code Playgroud)

注意:

Constant G还真不是.人们可以重新诠释G别的东西:

Planet.G = 1
Run Code Online (Sandbox Code Playgroud)

如果你真的需要它是常量(也就是不可重新绑定),那么使用新的aenum库 [1]将阻止尝试重新分配constants和Enum成员.


1披露:我是Python stdlibEnum,enum34backportAdvanced Enumeration(aenum) 库的作者.

  • @ZeroPiraeus:它更简单,更容易阅读,因此更多*pythonic*;).如果你想要一个好的`__repr__`,加一个.我已经做了. (2认同)

dno*_*zay 15

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

  • 基类,提供所有实现所需的行为,例如SatellitePlanet.
  • 如果您决定提供可选行为(例如Satellite,Planet可能必须提供不同的行为),mixins很有趣

这是一个示例,您首先定义您的行为:

#
# 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)
Run Code Online (Sandbox Code Playgroud)

然后Enum用正确的基类/ mixins 创建你的.

#
# 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)
Run Code Online (Sandbox Code Playgroud)


Ant*_*ala 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)
Run Code Online (Sandbox Code Playgroud)

@property不工作和classconstant DOES工作的原因很简单,并在这里答案中解释

通过类Hello.foo访问实际属性对象时返回的原因在于属性如何实现 __get__(self, instance, owner)特殊方法.如果在实例上访问描述符,则该实例作为适当的参数传递,owner是该实例的类.

另一方面,如果通过类访问它,则实例为None并且仅传递所有者.属性对象识别此并返回self.

因此,代码classproperty实际上是property缺少if instance is None部分的概括.


Zer*_*eus 5

Aproperty可用于提供类常量的大部分行为:

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)
Run Code Online (Sandbox Code Playgroud)

如果您想定义大量常量,这会有点麻烦,因此您可以在类外部定义一个辅助函数:

def constant(c):
    """Return a class property that returns `c`."""
    return property(lambda self: c)
Run Code Online (Sandbox Code Playgroud)

...并按如下方式使用它:

class Planet(Enum):

    # ...

    G = constant(6.67300E-11)
Run Code Online (Sandbox Code Playgroud)

这种方法的一个限制是它仅适用于类的实例,而不适用于类本身:

>>> Planet.EARTH.G
6.673e-11
>>> Planet.G
<property object at 0x7f665921ce58>
Run Code Online (Sandbox Code Playgroud)