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