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

Ethan Furman ethan at stoneleaf.us
Sun Apr 24 12:04:59 EDT 2016


On 04/24/2016 08:20 AM, Ian Kelly wrote:
> On Sun, Apr 24, 2016 at 1:20 AM, Ethan Furman 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.

All non-dunder methods, at least.

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

How does Java share enums with other programs, computers, and/or languages?

As far as value-separate-from-instance: if you want/need them to be the 
same thing, mix-in the type:

class Planet(float, Enum):
     ...

[see below for "no-alias" ideas/questions]

NB: The enum and the value are still different ('is' fails) but equal.

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

One reason for this is that Python enums are lookup-able via the value:

 >>> Planet(9.80265274333129)
Planet.EARTH

Do Java enums not have such a feature, or this "feature" totally 
unnecessary in Java?

I could certainly add a "no-alias" feature to aenum.  What would be the 
appropriate value-lookup behaviour in such cases?

- return the first match
- return a list of matches
- raise an error
- disable value-lookups for that Enum

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

Unfortunately, the empty tuple tends to be a singleton, so there is no 
way to tell that red and default are (supposed to be) the same and blue 
is (supposed to be) different:

--> a = b = ()
--> c = ()
--> a is b
True
--> a is c
True

If you have an idea on how to make that work I am interested.

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

Cool.  The stdlib Enum (and therefore the enum34 backport) is unlikely 
to change much.  However, aenum has a few fun things going on, and I'm 
happy to add more:

- NamedTuple (metaclass-based)
- NamedConstant (no aliases, no by-value lookups)
- Enum
   - magic auto-numbering
       class Number(Enum, auto=True):
          one, two, three
          def by_seven(self):
              return self.value * 7
   - auto-setting of attributes
        class Planet(Enum, init='mass radius'):
              MERCURY = 3.303e23, 2.4397e6
              EARTH = 5.976e24, 6.37814e6
              NEPTUNE = 1.024e26, 2.4746e7
        --> Planet.EARTH.mass
        5.976e24

--
~Ethan~



More information about the Python-list mailing list