On 12/12/20 7:25 PM, Steven D'Aprano wrote:
On Sat, Dec 12, 2020 at 06:00:17PM -0800, Ethan Furman wrote:
Enum is great! Okay, okay, my opinion might be biased. ;)
There is one area where Enum is not great -- for a bunch of unrelated values.
I don't know how to interpret that. Surely *in practice* enums are always going to be related in some sense?
I certainly hope so -- that was one of the points in creating Enum.
I don't expect to create an enum class like this:
class BunchOfRandomStuff(Enum): ANIMAL = 'Mustela nivalis (Least Weasel)' PRIME = 503 EULER_MASCHERONI_CONSTANT = 0.5772156649015329 BEST_PICTURE_1984 = 'Amadeus' DISTANCE_MELBOURNE_SYDNEY = (16040497, "rack mount units")
Which is why I said, "Enum is not great for a bunch of unrelated values".
If I did create such an unusual collection of enums, what is the standard Enum lacking that makes it "not great"? It seems to work fine to me.
Lots of things work -- calling `__len__` instead of `len()` works, but `__len__` is not the best way to get the length of an object. Enums are not great for a bunch of unrelated values because: - duplicate values would all get aliased to one name - ordinary values should not be compared using `is` - standard Enums cannot be seamlessly used as their actual value (example in other email) [...]
I don't understand this. Are you suggesting that NamedValues will have a `type` attribute **like Enum**, or **in addition** to what Enum provides (value and name)?
To be honest, Enum may have a "type" attribute at this point, I don't remember. NamedValues would definitely have a "type" attribute whose primary purpose is to make the value attribute work. As an example, consider sre_constant.MAXREPEAT vs sre_constant.MAX_REPEAT (the only difference is the underscore -- took me a few moments to figure that out). The sre_constant._NamedIntConstant class adds a name attribute, and returns that as the repr(). ```
sre_constants.MAXREPEAT MAXREPEAT sre_constants.MAX_REPEAT MAX_REPEAT
Not very illuminating. I ended up getting the actual value by calling `int()` on them.
int(sre_constants.MAXREPEAT) 4294967295 int(sre_constants.MAX_REPEAT) 42
By adding a "type" attribute, getting something useful becomes a little easier:
@property def value(self): return self._type_(self) ``` or maybe ``` @property def value(self): return self._type_.__repr__(self) ```
unlike Enum, duplicates are allowed
Um, do you mean duplicate names? How will that work?
No, duplicate values -- but in an Enum the names given to the duplicate value become aliases to the original name/value, while duplicates in NamedValue would remain different objects.
unlike Enum, new values can be added after class definition
Is there a use-case for this?
Yes.
If there is such a use-case, could we not just given Enums an API for adding new values, rather than invent a whole new Enum-by-another-name?
While NamedValues have a similarities to Enum (.name, .value, human readable repr()), they are not Enums.
unlike Enum, a NamedValue can always be used as-is, even if no data type has been mixed in -- in other words, there is no functional difference between MyIntConstants(int, NamedValue) and MyConstants(NamedValue).
Sorry, I don't get that either. How can Enums not be used "as-is"? What does that mean?
It means that you can't do things with the actual value of Color.RED, whether that value is an int, a string, or a whatever, without going through the value attribute.
Are you suggesting that NamedValue subclasses will automatically insert `int` into their MRO?
No, I'm saying that a NamedValue subclass will have int, or string, or frozenset, or whatever the actual values' types are, in their mro: ``` def __new__(cls, value, name): actual_type = type(value) new_value_type = type(cls.__name__, (cls, type(value)), {}) obj = actual_type.__new__(new_value_type, value) obj._name_ = name obj._type_ = actual_type return obj ``` The subclasses are created on the fly. The production code will cache the new subclasses so they're only created once.
If sre_constants was using a new data type, it should probably be IntEnum instead. But sre_parse is a good candidate for NamedValues:
class K(NamedValues): DIGITS = frozenset("0123456789") [...snip additional name/value pairs...]
and in use:
>>> K.DIGITS K.DIGITS >>> K.DIGITS.name 'DIGITS' >>> K.DIGITS.value frozenset("0123456789")
Why not just use an Enum?
Why use a Counter instead of defaultdict instead of dict? Because, depending on the task, one is more appropriate than the others. -- ~Ethan~