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~

_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/KGG7J3VR7D3YWLKWARHAJYZUXEOUFUMU/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to