Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
158 views
in Technique[技术] by (71.8m points)

python - Is it possible to define a class constant inside an Enum?

Python 3.4 introduces a new module enum, which adds an enumerated type to the language. The documentation for enum.Enum provides an example to demonstrate how it can be extended:

>>> 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

This example also demonstrates a problem with Enum: in the surface_gravity() property method, a constant G is defined which would normally be defined at class level - but attempting to do so inside an Enum would simply add it as one of the members of the enum, so instead it's been defined inside the method.

If the class wanted to use this constant in other methods, it'd have to be defined there as well, which obviously isn't ideal.

Is there any way to define a class constant inside an Enum, or some workaround to achieve the same effect?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is advanced behavior which will not be needed in 90+% of the enumerations created.

According to the docs:

The rules for what is allowed are as follows: _sunder_ names (starting and ending with a single underscore) are reserved by enum and cannot be used; all other attributes defined within an enumeration will become members of this enumeration, with the exception of __dunder__ names and descriptors (methods are also descriptors).

So if you want a class constant you have several choices:

  • create it in __init__
  • add it after the class has been created
  • use a mixin
  • create your own descriptor

Creating the constant in __init__ and adding it after the class has been created both suffer from not having all the class info gathered in one place.

Mixins can certainly be used when appropriate (see dnozay's answer for a good example), but that case can also be simplified by having a base Enum class with the actual constants built in.

First, the constant that will be used in the examples below:

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)

And the single-use Enum example:

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

And, finally, the multi-use Enum example:

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

Note:

The Constant G really isn't. One could rebind G to something else:

Planet.G = 1

If you really need it to be constant (aka not rebindable), then use the new aenum library [1] which will block attempts to reassign constants as well as Enum members.


1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...