Vladius
Vladius

Reputation: 4817

Convert string to Enum in Python

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?

Upvotes: 383

Views: 326256

Answers (10)

kernelplv
kernelplv

Reputation: 181

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

Upvotes: 6

Javed
Javed

Reputation: 6239

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

Upvotes: 3

Tzane
Tzane

Reputation: 3462

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

Upvotes: 6

Aneri Gandhi
Aneri Gandhi

Reputation: 71

Change your class signature to this:

class BuildType(str, Enum):

See enum documentation

Upvotes: 3

maciek
maciek

Reputation: 3354

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.

Upvotes: 1

Ethan Furman
Ethan Furman

Reputation: 69021

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()]

Upvotes: 618

Mitch
Mitch

Reputation: 1856

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`

Upvotes: 17

ADR
ADR

Reputation: 1291

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

Upvotes: 17

hvqzao
hvqzao

Reputation: 1045

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

Upvotes: 2

rogueleaderr
rogueleaderr

Reputation: 4799

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

Upvotes: 50

Related Questions