Skip to content

Enum: using _missing_ on Enums without any elements doesn't work since 3.11.6 #111103

Closed as not planned

Description

@mikrodust-henrikp

Bug report

Bug description:

It seems the pull request (#108704) that tried to handle erroneous calls to Enum.__new__ introduced some other behavior as well. In a few projects that I'm involved with we use this pattern:

from enum import Enum

class Result(Enum):
    @classmethod
    def _missing_(cls, value):
        v = None
        for subclass in cls.__subclasses__():
            try:
                v = subclass(value)
            except:
                pass

        return v


class CommonResult(Result):
    OK = 0
    ERR_FOO = -0x1
    ERR_BAR = -0x2

where calling Result(0) would return CommonResult.OK. Now in 3.11.6 and onwards this throws a TypeError. Was this intentional? It's a nice pattern to have and there are to me no obvious ways to replace it with something as elegant, so I hope that it can be solved some other way (maybe by checking if the class actually overrides __new__ and if not, then allowing the call to _missing_?)

CPython versions tested on:

3.11, 3.12

Operating systems tested on:

Linux, Windows

Activity

  1. added
    stdlibStandard Library Python modules in the Lib/ directory
    on Oct 20, 2023
  2. mikrodust-henrikp commented on Oct 20, 2023

    @mikrodust-henrikp
    Author

    Found a workaround which is not horrible. I still think that it would be nice for a few other use cases if the _missing_ pattern worked, but if anyone else needs it, here's a solution that replicates what at least I did:

    from enum import Enum, EnumMeta
    
    class Fudgeulator(EnumMeta):
        def __call__(cls, value, *args, **kwargs):
            for subclass in cls.__subclasses__():
                try:
                    return subclass(value)
                except Exception:
                    pass
            
            return super().__call__(value, *args, **kwargs)
    
    class Result(Enum, metaclass=Fudgeulator):
        pass
    
    class Foo(Result):
        pass
    
    class CommonResult(Foo):
        OK = 0
        ERR_FOO = -0x1
        ERR_BAR = -0x2
    
    class AnotherResult(Result):
        ERR_QUUX = -0x4
        ERR_BAAA = -0x5
    
    
    print(Result(-0x4))
    print(CommonResult.OK)
  3. ethanfurman commented on Oct 25, 2023

    @ethanfurman
    Member

    @mikrodust-henrikp: Definitely an intriguing use-case. Your subclass of EnumType is the correct solution (and hopefully not onerous).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

stdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions