395

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?

1
  • Contra the original version of the question, this is not a "serialization" task. Commented Jul 3, 2022 at 20:49

10 Answers 10

638

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()]
Sign up to request clarification or add additional context in comments.

6 Comments

What about a fallback value in case the input needs to be sanitised? Something in the sort of Build.get('illegal', Build.debug)?
@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.
@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).
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).
@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.
|
53

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')

3 Comments

Is there any way to overwrite the __getitem__ or some other built-in method?
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
@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?
18
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

3 Comments

I mean I would like to convert a debug string to an enum of such: python class BuildType(Enum): debug = 200 release = 400
Great tips! Is using __dict__ the same as getattr? I'm worrying about name collisions with internal Python attributes....
Oh... yes it the same as getattr. I see no reason for name collisions. You just can't set keyword as field of class.
17

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`

1 Comment

Today, do SignInMethod('EMAIL') had the same effect which that method
9
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

2 Comments

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
I do love it, but as a Python beginner, I'd appreciate some explanations.
4

In Python 3.11 you could also use a StrEnum. Granted, you would have to replace the values with strings as well, since it keyes on the value, not name (using enum.auto() for the value is an ok solution).

import enum

class BuildType(enum.StrEnum):
    debug = "200"
    release = "400"

print(BuildType["debug"])
print(BuildType("200"))

2 Comments

Very important to note that StrEnum.__getitem__() does NOT work like Enum.__getitem__() so for a StrEnum using BuildType["debug"] will always result in a KeyError and you have to use the syntax you've shown here with BuildType("debug") instead. This is exactly the opposite of an regular Enum that was assigned strings as the values.
Example code prints 200 for both cases with Python 3.11 and 3.13, BuildType("debug") will raise a ValueError.
3

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.

Comments

2

Change your class signature to this:

class BuildType(str, Enum):

See enum documentation

2 Comments

Could you add more detail? How would one then use the class?
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
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

Comments

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')

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.