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

AltStyle によって変換されたページ (->オリジナル) /