This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Problems with overriding Enum.__new__
Type: behavior Stage: resolved
Components: Documentation, Library (Lib) Versions: Python 3.4
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: docs@python Nosy List: Drekin, barry, docs@python, eli.bendersky, ethan.furman
Priority: normal Keywords:

Created on 2013-09-17 10:10 by Drekin, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (7)
msg197979 - (view) Author: Adam Bartoš (Drekin) * Date: 2013-09-17 10:10
I tried to implement an Enum variant OptionalEnum such that OptionalEnum(value) leaves value alone and returnes it (if there is no corresponding enum member) rather than raising ValueError. My use case is following: I'm trying to parse some keyboard layout related data. There is a table which maps virtual keys to unicode codepoints, however there are some special values which don't represent a codepoint but the fact that the key is a deadkey and more information is provided in the next table entry. Or that the key produces a ligature. So I wanted to define enums for those special values.

This brought me to the following naive approach:
class OptionalEnum(Enum):
    def __new__(cls, value):
        try:
            return super(cls, cls).__new__(cls, value)
        except ValueError:
            return value

This ends with a weird exception. As I ran through enum code I found out that there are actually two flavors of EnumSubclass.__new__. The first one is for precreating the enum members (during creation of EnumSubclass). This is the intended usage. The second flavor is what happens when EnumSubclass(value) is called later. This flavor is represented by Enum.__new__ which returns existing member corresponding to the value or raises ValueError. However this Enum.__new__ is rather special. It doesn't take place in the process of the first flavor, it is explicitly by-passed. On the other hand it is forced behavior of the second flavor since it is explicitly assigned to EnumSubclass.__new__. So there seems to be no way to customize the second flavor (as I tried in my use case).

So could the customization of the second flavor of __new__ be added? (One maybe naive solution would be to let user explicitly define __new_member__ rather than __new__ to customize the first flavor and leave __new__ for the second flavor.) Or could at least be done something with the exceptions produced when one tries to? (The exception in my case was 'TypeError: object() takes no parameters' and was result of having custom __new__ and hence use_args==True but having member_type==object and __new__ not creating member with _value_ attribute so object(*args) was called.)

In any case additional documentation on customizing __new__ could help. There are points like that one should avoid the pattern of falling back to super().__new__ since that is Enum.__new__ which is for something else. Also the custom __new__ should return an object with _value_ attribute or object() with some argument may be called which leads to uninformative exception.
msg198311 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-09-23 01:40
How about this note after the AutoNumber example?

.. note::

    The :meth:`__new__` method, if defined, is used during creation of the Enum
    members; it is then replaced by Enum's :meth:`__new__` which is used after
    class creation for lookup of existing members.  Due to the way Enums are
    supposed to behave, there is no way to customize Enum's :meth:`__new__`.
msg198312 - (view) Author: Eli Bendersky (eli.bendersky) * (Python committer) Date: 2013-09-23 02:26
LGTM, Ethan. You know how I feel about customization in general ;-) We should give Enum a term or two in the stdlib to learn how it's being used and abused - we can always *add* customization in the future.
msg198314 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-09-23 04:04
Yup, just trying to add some explanation on how it currently works.

Drekin, I'm sure you've already figured this out, but for those who may read this in the future:  what you need is a helper function:

def OptionalEnum(value):
    "could also be OptionalEnum(enum, value)"
    try:
        return SomeEnum(value) # or return enum(value)
    except ValueError:
        return value
msg198317 - (view) Author: Adam Bartoš (Drekin) * Date: 2013-09-23 08:04
Yes, I've done it similarily using a class method. Thank you for help.
msg198320 - (view) Author: Eli Bendersky (eli.bendersky) * (Python committer) Date: 2013-09-23 13:57
On Sun, Sep 22, 2013 at 9:04 PM, Ethan Furman <report@bugs.python.org>wrote:

>
> Ethan Furman added the comment:
>
> Yup, just trying to add some explanation on how it currently works.
>
> Drekin, I'm sure you've already figured this out, but for those who may
> read this in the future:  what you need is a helper function:
>
> def OptionalEnum(value):
>     "could also be OptionalEnum(enum, value)"
>     try:
>         return SomeEnum(value) # or return enum(value)
>     except ValueError:
>         return value
>

Hmm, looks suspiciously similar to _inienum_converter from socket.py ;-)
msg198375 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2013-09-25 03:09
Doc patch is in #19011.  I'll close this one when that one is closed.
History
Date User Action Args
2022-04-11 14:57:51adminsetgithub: 63240
2013-09-28 05:59:24ethan.furmansetstatus: open -> closed
resolution: fixed
stage: patch review -> resolved
2013-09-25 03:09:39ethan.furmansetmessages: + msg198375
2013-09-23 13:57:27eli.benderskysetmessages: + msg198320
2013-09-23 08:04:07Drekinsetmessages: + msg198317
2013-09-23 04:04:06ethan.furmansetmessages: + msg198314
2013-09-23 02:26:49eli.benderskysetmessages: + msg198312
2013-09-23 01:40:35ethan.furmansetnosy: + barry, eli.bendersky

messages: + msg198311
stage: patch review
2013-09-17 14:00:52ethan.furmansetnosy: + ethan.furman
2013-09-17 10:10:37Drekincreate