Comparing Python enums to Java, was: How much sanity checking is required for function inputs?

Ian Kelly ian.g.kelly at gmail.com
Sun Apr 24 11:20:24 EDT 2016


On Sun, Apr 24, 2016 at 1:20 AM, Ethan Furman <ethan at stoneleaf.us> wrote:
> On 04/23/2016 06:29 PM, Ian Kelly wrote:
>
>> Python enums are great. Sadly, they're still not quite as awesome as Java
>> enums.
>
>
> What fun things can Java enums do?

Everything that Python enums can do, plus:

* You can override methods of individual values, not just the class as
a whole. Good for implementing the strategy pattern, or for defining a
default method implementation that one or two values do differently.
In Python you can emulate the same thing by adding the method directly
to the instance dict of the enum value, so this isn't really all that
much of a difference.

* Java doesn't have the hokey notion of enum instances being distinct
from their "value". The individual enum members *are* the values.
Whereas in Python an enum member is an awkward class instance that
contains a value of some other type. Python tries to get away from the
C-like notion that enums are ints by making the enum members
non-comparable, but then gives us IntEnum as a way to work around it
if we really want to. Since Java enums don't depend on any other type
for their values, there's nothing inviting the user to treat enums as
ints in the first place.

* As a consequence of the above, Java doesn't conflate enum values
with their parameters. The Python enum docs give us this interesting
example of an enum that takes arguments from its declaration:

>>> 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 is incredibly useful, but it has a flaw: the value of each member
of the enum is just the tuple of its arguments. Suppose we added a
value for COUNTER_EARTH describing a hypothetical planet with the same
mass and radius existing on the other side of the sun. [1] Then:

>>> Planet.EARTH is Planet.COUNTER_EARTH
True

Because they have the same "value", instead of creating a separate
member, COUNTER_EARTH gets defined as an alias for EARTH. To work
around this, one would have to add a third argument to the above to
pass in an additional value for the sole purpose of distinguishing (or
else adapt the AutoNumber recipe to work with this example). This
example is a bit contrived since it's generally not likely to come up
with floats, but it can easily arise (and in my experience frequently
does) when the arguments are of more discrete types. It's notable that
the Java enum docs feature this very same example but without this
weakness. [2]

* Speaking of AutoNumber, since Java enums don't have the
instance/value distinction, they effectively do this implicitly, only
without generating a bunch of ints that are entirely irrelevant to
your enum type. With Python enums you have to follow a somewhat arcane
recipe to avoid specifying values, which just generates some values
and then hides them away. And it also breaks the Enum alias feature:

>>> class Color(AutoNumber):
...     red = default = ()  # not an alias!
...     blue = ()
...
>>> Color.red is Color.default
False

Anyroad, I think that covers all my beefs with the way enums are
implemented in Python. Despite the above, they're a great feature, and
I use them and appreciate that we have them.

[1] https://en.wikipedia.org/wiki/Counter-Earth
[2] https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html


More information about the Python-list mailing list