331

What's the correct way to convert a string to a corresponding instance of an Enum subclass? Seems like getattr(YourEnumType, str) does the job, but I'm not sure if it's safe enough.

As an example, suppose I have an enum like

class BuildType(Enum):
    debug = 200
    release = 400

Given the string 'debug', how can I get BuildType.debug as a result?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
Vladius
  • 4,268
  • 2
  • 20
  • 21

9 Answers9

538

This functionality is already built in to Enum:

>>> from enum import Enum
>>> class Build(Enum):
...   debug = 200
...   build = 400
... 
>>> Build['debug']
<Build.debug: 200>

The member names are case sensitive, so if user-input is being converted you need to make sure case matches:

an_enum = input('Which type of build?')
build_type = Build[an_enum.lower()]
Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 12
    What about a fallback value in case the input needs to be sanitised? Something in the sort of `Build.get('illegal', Build.debug)`? – Hetzroni Feb 05 '18 at 10:19
  • 1
    @Hetzroni: `Enum` does not come with a `.get()` method, but you can add one as needed, or just make a base `Enum` class and always inherit from that. – Ethan Furman Feb 05 '18 at 16:29
  • 3
    @Hetzroni: Per the "ask for forgiveness, not for permission" principle, you can always envelop the access in a try/except KeyError clause to return the default (and as Ethan mentioned, optionally wrap this up in your own function/method). – Laogeodritt Feb 09 '18 at 22:37
  • Worth noting here - if using this for serialization / deserialization, serialize the `name` property for this, so use `Build.debug.name` rather than `str(Build.debug)` for this sort of lookup to work (otherwise it tries to find `Build.debug` on the deserialization side which won't exist). – fquinner Oct 09 '19 at 11:22
  • 11
    @Dragonborn It wouldn't work to call `Build('debug')`. The class constructor must take the *value*, i.e. `200` or `400` in this example. To pass the *name* you must use square brackets, as the answer already says. – Arthur Tacca Jan 20 '20 at 14:34
  • It's worth noting that common case is to have enums with values equal the labels, like `debug = 'debug'`. In such cases getting an enum value by `Build('debug')` is preferred over `Build['debug']` as it raises a `ValueError` in case of an incorrect value (instead of a `KeyError`). – maciek Jul 28 '23 at 09:40
45

Another alternative (especially useful if your strings don't map 1-1 to your enum cases) is to add a staticmethod to your Enum, e.g.:

class QuestionType(enum.Enum):
    MULTI_SELECT = "multi"
    SINGLE_SELECT = "single"

    @staticmethod
    def from_str(label):
        if label in ('single', 'singleSelect'):
            return QuestionType.SINGLE_SELECT
        elif label in ('multi', 'multiSelect'):
            return QuestionType.MULTI_SELECT
        else:
            raise NotImplementedError

Then you can do question_type = QuestionType.from_str('singleSelect')

rogueleaderr
  • 4,671
  • 2
  • 33
  • 40
  • 2
    Is there any way to overwrite the `__getitem__` or some other built-in method? – ClementWalter Nov 04 '20 at 17:10
  • I think the compiler or at least Pycharm thinks the from_str function is returning a str type. Using QuestionType["some_string"], as described in a different answer, has Pycharm thinking its a QuestionType type – user1689987 Feb 01 '23 at 01:04
  • @ClementWalter yes, but since it needs to be implemented *on the enum type itself, not on instances*, you need to do it in a metaclass. The thing is - `enum.Enum` *already does this for you*. You *could* subclass `enum.Enum`'s metaclass, which is also exposed as `enum.EnumType`, and override its `__getitem__` - but do you actually need it to do something different from the default? – Karl Knechtel Aug 30 '23 at 22:11
16
def custom_enum(typename, items_dict):
    class_definition = """
from enum import Enum

class {}(Enum):
    {}""".format(typename, '\n    '.join(['{} = {}'.format(k, v) for k, v in items_dict.items()]))

    namespace = dict(__name__='enum_%s' % typename)
    exec(class_definition, namespace)
    result = namespace[typename]
    result._source = class_definition
    return result

MyEnum = custom_enum('MyEnum', {'a': 123, 'b': 321})
print(MyEnum.a, MyEnum.b)

Or do you need to convert string to known Enum?

class MyEnum(Enum):
    a = 'aaa'
    b = 123

print(MyEnum('aaa'), MyEnum(123))

Or:

class BuildType(Enum):
    debug = 200
    release = 400

print(BuildType.__dict__['debug'])

print(eval('BuildType.debug'))
print(type(eval('BuildType.debug')))    
print(eval(BuildType.__name__ + '.debug'))  # for work with code refactoring
ADR
  • 1,255
  • 9
  • 20
  • I mean I would like to convert a `debug` string to an enum of such: ```python class BuildType(Enum): debug = 200 release = 400 ``` – Vladius Dec 31 '16 at 11:29
  • Great tips! Is using `__dict__` the same as `getattr`? I'm worrying about name collisions with internal Python attributes.... – Vladius Dec 31 '16 at 12:38
  • Oh... yes it the same as `getattr`. I see no reason for name collisions. You just can't set keyword as field of class. – ADR Dec 31 '16 at 13:17
14

My Java-like solution to the problem. Hope it helps someone...

from enum import Enum, auto


class SignInMethod(Enum):
    EMAIL = auto(),
    GOOGLE = auto()

    @classmethod
    def value_of(cls, value):
        for k, v in cls.__members__.items():
            if k == value:
                return v
        else:
            raise ValueError(f"'{cls.__name__}' enum not found for '{value}'")


sim = SignInMethod.value_of('EMAIL')
assert sim == SignInMethod.EMAIL
assert sim.name == 'EMAIL'
assert isinstance(sim, SignInMethod)
# SignInMethod.value_of("invalid sign-in method")  # should raise `ValueError`
Mitch
  • 1,556
  • 1
  • 17
  • 17
2

An improvement to the answer of @rogueleaderr :

class QuestionType(enum.Enum):
    MULTI_SELECT = "multi"
    SINGLE_SELECT = "single"

    @classmethod
    def from_str(cls, label):
        if label in ('single', 'singleSelect'):
            return cls.SINGLE_SELECT
        elif label in ('multi', 'multiSelect'):
            return cls.MULTI_SELECT
        else:
            raise NotImplementedError
Javed
  • 5,904
  • 4
  • 46
  • 71
1

Since MyEnum['dontexist'] will result in error KeyError: 'dontexist', you might like to fail silently (eg. return None). In such case you can use the following static method:

class Statuses(enum.Enum):
    Unassigned = 1
    Assigned = 2

    @staticmethod
    def from_str(text):
        statuses = [status for status in dir(
            Statuses) if not status.startswith('_')]
        if text in statuses:
            return getattr(Statuses, text)
        return None


Statuses.from_str('Unassigned')
nme
  • 1,025
  • 10
  • 8
1

Change your class signature to this:

class BuildType(str, Enum):
Robson
  • 813
  • 5
  • 21
  • 40
  • 8
    Could you add more detail? How would one then use the class? – Cleb Aug 22 '21 at 20:38
  • 4
    The author did not further clarify. When you declare your class in this fashion, you can use the enum string values to create direct instances of the enum value. you can have A = "FIRST_VALUE" - then doing BuildType("FIRST_VALUE") will get you BuildType.A automatically. It only applies to your use case if the string values are the same as the enum name – Eric Castro Nov 09 '22 at 20:31
1
class LogLevel(IntEnum):
    critical = logging.CRITICAL
    fatal = logging.FATAL
    error = logging.ERROR
    warning = logging.WARNING
    info = logging.INFO
    debug = logging.DEBUG
    notset = logging.NOTSET

    def __str__(self):
        return f'{self.__class__.__name__}.{self.name}'

    @classmethod
    def _missing_(cls, value):
        if type(value) is str:
            value = value.lower()
            if value in dir(cls):
                return cls[value]

        raise ValueError("%r is not a valid %s" % (value, cls.__name__))

Example:

print(LogLevel('Info'))
print(LogLevel(logging.WARNING))
print(LogLevel(10))    # logging.DEBUG
print(LogLevel.fatal)
print(LogLevel(550))

Output:

LogLevel.info
LogLevel.warning
LogLevel.debug
LogLevel.critical
ValueError: 550 is not a valid LogLevel
kernelplv
  • 111
  • 2
  • 4
  • 1
    wow, a rare _sunder_. This answer has the benefit of being "drop in", and thus works with existing "structuring" libraries, in my case cattrs, without any customization. Thanks – toppk Aug 09 '23 at 20:33
0

Getting enum instance by name

If you want to access enum members by name, use item access:

>>> from enum import Enum
>>> class Build(Enum):
...   debug = 200
...   build = 400
... 
>>> Build['debug']
<Build.debug: 200>

It raises KeyError for unexpected name value.

Getting enum instance by value

For following Enum class, where the values are strings:

class BuildType(Enum):
    debug = 'debug'
    release = 'release'

An enumeration uses call syntax to return members by value:

>>> BuildType('debug')
<BuildType.debug: 'debug'>

It raises ValueError for unexpected value.

maciek
  • 3,198
  • 2
  • 26
  • 33