I do agree that this is a worthwhile feature-ish, but it's marred by the 
tuples-for-values assumption. We've build very much the same thing in tri.token 
(https://github.com/TriOptima/tri.token) but tri.token is much more scalable to 
many arguments because it uses keyword arguments and not positional arguments. 

We have "enums" that declare rich static data with more than ten arguments. 
This just doesn't work for positional arguments. 

> On 28 Oct 2019, at 03:13, Steve Jorgensen <ste...@stevej.name> wrote:
> 
> Here's a working example of what I'm talking about. In this example, `EnumX` 
> is a tweak of `Enum` to that has the capability that I'm talking about, and I 
> have also pasted the code for `EnumX` later in this email.
> 
> Please note that the important thing in this example is NOT what this 
> `ChoiceEnum` class does but the ability to create custom enums that can do 
> custom processing of assigned value/key such as what `ChoiceEnum` does, using 
> a documented feature of `Enum`, without having to either write and debug a 
> complicated hacks (that might not work with future updates to `Enum`) or 
> replace hunks of `Enum` (as my `EnumX` shown here does) in ways that might 
> not work or might not be feature-complete with future `Enum` updates.
> 
>    # Example usage of enhanced Enum with _preprocess_value_ support.
> 
>    def _label_from_key(key):
>        return key.replace('_', ' ').title()
> 
> 
>    class _ChoiceValue(str):
>        """Easy way to have an object with immutable string content that is
>        identified for equality by its content with a label attribute that is
>        not significant for equality.
>        """
>        def __new__(cls, content, label):
>            obj = str.__new__(cls, content)
>            obj.label = label
>            return obj
> 
>        def __repr__(self):
>            content_repr = str.__repr__(self)
>            return '%s(%r, %r)' % (
>                self.__class__.__name__, content_repr, self.label)
> 
>        def get_value_label_pair(self):
>            return (f'{self}', self.label)
> 
> 
>    class ChoiceEnum(EnumX):
>        def _preprocess_value_(key, src):
>            value = None
>            label = None
>            if src == ():
>                pass
>            elif isinstance(src, tuple) and src != ():
>                value, label = src
>            elif isinstance(str, str) and src.startswith(','):
>                label = src[1:]
>            else:
>                value = src
>            if value is None:
>                value = key
>            label = label or _label_from_key(key)
>            return _ChoiceValue(value, label)
> 
>        def __getitem__(self, key):
>            return (f'{self._value_}', self._value_.label).__getitem__(key)
> 
>        def __len__(self):
>            return 2
> 
>        @property
>        def value(self):
>            return self._value_.get_value_label_pair()
> 
>        @property
>        def label(self):
>            return self._value_.label
> 
> 
>    class Food(ChoiceEnum):
>        APPLE =     ()
>        CHEESE =    ()
>        HAMBURGER = 'BURGER'
>        SOUFFLE =   ',Soufflé'
>        CHICKEN_MCNUGGETS = ('CHX_MCNUG', 'Chicken McNuggets')
>        DEFAULT = 'APPLE'
> 
> 
>    for food in Food:
>        print(repr(food))
>    # Prints...
>    # <Food.APPLE: _ChoiceValue("'APPLE'", 'Apple')>
>    # <Food.CHEESE: _ChoiceValue("'CHEESE'", 'Cheese')>
>    # <Food.HAMBURGER: _ChoiceValue("'BURGER'", 'Hamburger')>
>    # <Food.SOUFFLE: _ChoiceValue("',Soufflé'", 'Souffle')>
>    # <Food.CHICKEN_MCNUGGETS: _ChoiceValue("'CHX_MCNUG'", 'Chicken 
> McNuggets')>
> 
>    print(f'Food.DEFAULT is Food.APPLE: {Food.DEFAULT is Food.APPLE}')
>    # Prints...
>    # Food.DEFAULT is Food.APPLE: True
> 
> Here's the implementation of `EnumX` that is being used for the above. There 
> are just a handful of those lines that represent changes to the 
> implementation at https://github.com/python/cpython/blob/3.8/Lib/enum.py and 
> I there are inline comments to draw attention to those.
> 
>    from enum import (
>        Enum, EnumMeta, auto, _is_sunder, _is_dunder, _is_descriptor, 
> _auto_null)
> 
> 
>    # Copy of EnumDict with tweaks to support _preprocess_value_
>    class _EnumDictX(dict):
>        def __init__(self):
>            super().__init__()
>            self._member_names = []
>            self._last_values = []
>            self._ignore = []
> 
> 
>        def __setitem__(self, key, value):
>            """Duplicate all of _EnumDict.__setitem__ in order to insert a hook
>            """
>            if _is_sunder(key):
>                if key not in (
>                        '_order_', '_create_pseudo_member_',
>                        '_generate_next_value_', '_missing_', '_ignore_',
>                        '_preprocess_value_',  # <--
>                        ):
>                    raise ValueError('_names_ are reserved for future Enum 
> use')
>                if key == '_generate_next_value_':
>                    setattr(self, '_generate_next_value', value)
>                # ====================
>                if key == '_preprocess_value_':
>                    setattr(self, '_preprocess_value', value)
>                # ====================
>                elif key == '_ignore_':
>                    if isinstance(value, str):
>                        value = value.replace(',', ' ').split()
>                    else:
>                        value = list(value)
>                    self._ignore = value
>                    already = set(value) & set(self._member_names)
>                    if already:
>                        raise ValueError(
>                            '_ignore_ cannot specify already set names: %r' % (
>                                already, ))
>            elif _is_dunder(key):
>                if key == '__order__':
>                    key = '_order_'
>            elif key in self._member_names:
>                # descriptor overwriting an enum?
>                raise TypeError('Attempted to reuse key: %r' % key)
>            elif key in self._ignore:
>                pass
>            elif not _is_descriptor(value):
>                if key in self:
>                    # enum overwriting a descriptor?
>                    raise TypeError('%r already defined as: %r' % (key, 
> self[key]))
>                # ====================
>                value = self._preprocess_value(key, value)
>                # ====================
>                if isinstance(value, auto):
>                    if value.value == _auto_null:
>                        value.value = self._generate_next_value(
>                            key, 1, len(self._member_names), 
> self._last_values[:])
>                    value = value.value
>                self._member_names.append(key)
>                self._last_values.append(value)
>            dict.__setitem__(self, key, value)
> 
> 
>    # Subclass of EnumMeta with tweak to support _preprocess_value_
>    class EnumMetaX(EnumMeta):
>        # Copy of EnumMeta.__prepare__ with tweak to support _preprocess_value_
>        @classmethod
>        def __prepare__(metacls, cls, bases):
>            # create the namespace dict
>            enum_dict = _EnumDictX()
>            # inherit previous flags and _generate_next_value_ function
>            member_type, first_enum = metacls._get_mixins_(bases)
>            if first_enum is not None:
>                # ====================
>                enum_dict['_preprocess_value_'] = getattr(
>                    first_enum, '_preprocess_value_', None)
>                # ====================
>                enum_dict['_generate_next_value_'] = getattr(first_enum, 
> '_generate_next_value_', None)
>            return enum_dict
> 
> 
>    # Subclass of Enum using EnumMetaX as metaclass and with default
>    # implementation of _preprocess_value_.
>    class EnumX(Enum, metaclass=EnumMetaX):
>        def _preprocess_value_(key, value):
>            return value
> _______________________________________________
> 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/INFWIWTHEP6HO33LOOPBW6OZ22T4ZMSA/
> Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
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/EKWWOAARGQTK5V25QRJZXEV3HGOJR2YU/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to