Years ago, when I wrote this post about Python enums I mentioned this:
if you want something more advanced like the Java/Kotlin enums, where we can have multiple attributes as values and have instance methods (the archetypical Java Planets example), you can use the powerful aenum module (advanced enums), that just implements the Planets example.
Well, I've just found out that the standard Python enums support "enum members with associated data". You can add methods and properties to your enum classes, so you have a functionality pretty similar to the powerful Java enums. I've felt a bit like an idiot, cause after learning this from a code discussion with a GPT I realised that it's not any "python hidden feature", the standard enums documentation comes with a Planet example.
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
Planet.EARTH.surface_gravity
That's the equivalent to the Java Planet one that years ago showed the world that enums could be much more than what we were used to.
public enum Planet {
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);
private final double mass; // in kilograms
private final double radius; // in meters
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
}
private double mass() { return mass; }
private double radius() { return radius; }
// universal gravitational constant (m3 kg-1 s-2)
public static final double G = 6.67300E-11;
double surfaceGravity() {
return G * mass / (radius * radius);
}
double surfaceWeight(double otherMass) {
return otherMass * surfaceGravity();
}
"Traditional" enums, like those in C or C# are just "a group of named constant values with type safety", they answered this question "How do I give names to a set of integer constants?". Java enums are "classes with a fixed set of instances defined at compile time", and they answer this question "How do I represent a fixed set of domain objects?"
Before Java added enums to the language, people were using the "Typesafe Enum Pattern" pattern to simulate "object-oriented enums". By the way, years ago I wrote about emulating Java enums in C#.
Java enums have an extra feature that is not directly implemented by Python enums, each member of the enum can have its own implementation of some methods (it's a per-instance override). Let's see an example:
enum Operation {
PLUS { public int apply(int a, int b) { return a + b; } },
TIMES { public int apply(int a, int b) { return a * b; } };
public abstract int apply(int a, int b);
}
Python does not have a direct equivalent, a member can’t override a method. But there are idiomatic workarounds: carry the behaviour as data (a callable in the tuple) and dispatch on self inside one method:
class Operation(enum.Enum):
PLUS = (operator.add,)
TIMES = (operator.mul,)
def __init__(self, fn: Callable[[int, int], int]) -> None:
self.fn = fn
def apply(self, a: int, b: int) -> int:
return self.fn(a, b)
In Python we can dynamically look up an enum member both by name Color["RED"] and by value (Color("red")). The latter is not supported in Java. Related to this, we can customize behaviour when trying to access an element using a non existing value, by means of defining a _missing_ method:
from enum import Enum
class Color(Enum):
RED = "red"
BLUE = "blue"
@classmethod
def _missing_(cls, value):
value = value.lower()
for member in cls:
if member.value == value:
return member
return None
Color.RED is Color("rEd")
# True
No comments:
Post a Comment