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?
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()]
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')
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
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`
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
Change your class signature to this:
class BuildType(str, Enum):
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')
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
I just want to notify this does not work in python 3.6
class MyEnum(Enum):
a = 'aaa'
b = 123
print(MyEnum('aaa'), MyEnum(123))
You will have to give the data as a tuple like this
MyEnum(('aaa',))
EDIT:
This turns out to be false. Credits to a commenter for pointing out my mistake
Related
I have 3 dataclass objects say:
class Message1:
def __init__(a):
...
class Message2:
def __init__(d,e,f):
...
class Message3:
def __init__(g,i):
...
For these 3 messages I want to make a factory type method which can return one of the three objects if it succeeds and if not it should return either the one it identified as the correct message to be created but failed at creation or it should notify the user that it could not create any of the messages. Are there any OOP patterns for this?
My initial thought was to do a:
def factory_method(**parameters):
try:
Message1(**parameters)
except TypeError:
try:
Message2(**parameters)
except:
try:
Message3(**parameters)
except:
print("Could not deduce message type")
My issue with this idea is that:
It's not a dynamically scalable solution, with each new message class I introduce I need to add a new try catch block
If the whole nested block structure fails, I have no feedback as to why, was the parameters correct for one of the message but wrong value, or was it plain gibberish?
I realize this might be a bit opinion based on what the best outcome is. At the same time it might be the solution is not too elegant and the simplest way is to just tell the factory_method what kind of message to initialize. Any suggestions or ideas would be appreciated.
If you can't join them all in a single class and you can't point a call to a single class, i would match the arguments to the posible class. To make it work a type hint and a "proxy" class is required. This example asumes that any of the classes wont contain a __init__(*args, **kwargs), and to add a new class you just add it to Message.msg_cls, you can eval the global scope if you don't want to add manually each class.
class Message1:
def __init__(self, a: int, alt=None, num=10):
print('Message 1')
class Message2:
def __init__(self, d: str, e: str, f: int):
print('Message 2')
class Message3:
def __init__(self, g: int, i: any):
print('Message 3')
class Message:
msg_cls = (
Message1,
Message2,
Message3
)
#staticmethod
def eq_kwargs(cls, kwargs):
cls_kwargs = cls.__init__.__defaults__
if cls_kwargs is None:
if len(kwargs) > 0:
return False
else:
return True
cls_astr = cls.__init__.__code__
kw_types = [type(t) for t in cls_kwargs]
for k in kwargs:
if k in cls_astr.co_varnames:
if type(kwargs[k]) in kw_types:
kw_types.remove(type(kwargs[k]))
else:
if type(None) in kw_types:
kw_types.remove(type(None))
else:
return False
else:
return False
return True
#staticmethod
def eq_args(cls, args):
cls_args = cls.__init__.__annotations__
if len(cls_args) != len(args):
return False
for a, b in zip(args, cls_args):
if type(a) != cls_args[b] and cls_args[b] != any:
return False
return True
def __new__(cls, *args, **kwargs):
for mc in Message.msg_cls:
if Message.eq_args(mc, args):
if Message.eq_kwargs(mc, kwargs):
return mc(*args, **kwargs)
raise ValueError('Message.__new__, no match')
if __name__ == '__main__':
ms_1_a = Message(1, alt='a')
ms_1_b = Message(2, alt='a', num=5)
ms_2 = Message('X', 'Y', 5)
ms_3_a = Message(1, [1, 4])
ms_3_b = Message(2, Message(10))
I have created the following Enum:
from enum import Enum
class Action(str, Enum):
NEW_CUSTOMER = "new_customer"
LOGIN = "login"
BLOCK = "block"
I have inherited from str, too, so that I can do things such as:
action = "new_customer"
...
if action == Action.NEW_CUSTOMER:
...
I would now like to be able to check if a string is in this Enum, such as:
if "new_customer" in Action:
....
I have tried adding the following method to the class:
def __contains__(self, item):
return item in [i for i in self]
However, when I run this code:
print("new_customer" in [i for i in Action])
print("new_customer" in Action)
I get this exception:
True
Traceback (most recent call last):
File "/Users/kevinobrien/Documents/Projects/crazywall/utils.py", line 24, in <module>
print("new_customer" in Action)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/enum.py", line 310, in __contains__
raise TypeError(
TypeError: unsupported operand type(s) for 'in': 'str' and 'EnumMeta'
I just bumped into this problem today (2020-12-09); I had to change a number of subpackages for Python 3.8.
Perhaps an alternative to the other solutions here is the following, inspired by the excellent answer here to a similar question, as well as #MadPhysicist's answer on this page:
from enum import Enum, EnumMeta
class MetaEnum(EnumMeta):
def __contains__(cls, item):
try:
cls(item)
except ValueError:
return False
return True
class BaseEnum(Enum, metaclass=MetaEnum):
pass
class Stuff(BaseEnum):
foo = 1
bar = 5
Tests (python >= 3.7; tested up to 3.10):
>>> 1 in Stuff
True
>>> Stuff.foo in Stuff
True
>>> 2 in Stuff
False
>>> 2.3 in Stuff
False
>>> 'zero' in Stuff
False
You can check if the enum contains a value by calling it:
>>> Action('new_customer')
Action.NEW_CUSTOMER
If the object you pass in is not guarantee to be in the enum, you can use a try block to capture the resulting ValueError. E.g.,
def is_action(obj):
try:
Action(obj)
except ValueError:
return False
return True
Given an enum of languages
class Language(enum.Enum):
en = 'en'
zh = 'zh'
#classmethod
def has_member_key(cls, key):
return key in cls.__members__
print(Language.has_member_key('tu')) => False
print(Language.has_member_key('en')) => True
Since Action is a derived class of Enum, we can use the fact that Enum has a member called _value2member_map_.
value2member_map is a private attribute (i.e. Internally in CPython) tthat maps values to names(will only work for hashable values though). However, it's not a good idea to rely on private attributes as they can be changed anytime.
Reference
We get the following:
if "new_customer" in Action._value2member_map_: # works
which is close to your desired:
if "new_customer" in Action: # doesn't work (i.e. TypeError)
You can use hasattr instead of in if you can get Enum member name with what you have.
action = "new_customer"
enum_member_name = action.upper()
if hasattr(Action, enum_member_name):
print(f"{action} in Action")
else:
print(f"{action} not in Action")
You can also check contains in enum by brackets like in dict
class Action(Enum):
NEW_CUSTOMER = 1
LOGIN = 2
BLOCK = 3
action = 'new_customer'
try:
action = Action[action.upper()]
print("action type exists")
except KeyError:
print("action type doesn't exists")
I generally use following code to have both functionality:
'service' in ENTITY_TYPES
ENTITY_TYPES.SERVICE in ENTITY_TYPES
from enum import Enum, EnumMeta
from typing import Any
class EnumeratorMeta(EnumMeta):
def __contains__(cls, member: Any):
if type(member) == cls:
return EnumMeta.__contains__(cls, member)
else:
try:
cls(member)
except ValueError:
return False
return True
class Enumerator(Enum, metaclass=EnumeratorMeta):
pass
class ENTITY_TYPES(Enumerator):
SERVICE: str = 'service'
CONFIGMAP: str = 'configmap'
I want to keep my enum classes generic (without altering internal functionality):
def clean_class_dict(class_dict):
return_dict = dict(class_dict)
for key in list(return_dict.keys()):
if key[0] == "_":
del return_dict[key]
return return_dict
def item_in_enum_titles(item: str, enum: Enum):
enum_dict = clean_class_dict(enum.__dict__)
if item in enum_dict.keys():
return True
else:
return False
I convert my enum to a dict and remove all the private functions and variables.
I'm wondering how could one create a program to detect the following cases in the code, when comparing a variable to hardcoded values, instead of using enumeration, dynamically?
class AccountType:
BBAN = '000'
IBAN = '001'
UBAN = '002'
LBAN = '003'
I would like the code to report (drop a warning into the log) in the following case:
payee_account_type = self.get_payee_account_type(rc) # '001' for ex.
if payee_account_type in ('001', '002'): # Report on unsafe lookup
print 'okay, but not sure about the codes, man'
To encourage people to use the following approach:
payee_account_type = self.get_payee_account_type(rc)
if payee_account_type in (AccountType.IBAN, AccountType.UBAN):
print 'do this for sure'
Which is much safer.
It's not a problem to verify the == and != checks like below:
if payee_account_type == '001':
print 'codes again'
By wrapping payee_account_type into a class, with the following __eq__ implemented:
class Variant:
def __init__(self, value):
self._value = value
def get_value(self):
return self._value
class AccountType:
BBAN = Variant('000')
IBAN = Variant('001')
UBAN = Variant('002')
LBAN = Variant('003')
class AccountTypeWrapper(object):
def __init__(self, account_type):
self._account_type = account_type
def __eq__(self, other):
if isinstance(other, Variant):
# Safe usage
return self._account_type == other.get_value()
# The value is hardcoded
log.warning('Unsafe comparison. Use proper enumeration object')
return self._account_type == other
But what to do with tuple lookups?
I know, I could create a convention method wrapping the lookup, where the check can be done:
if IbanUtils.account_type_in(account_type, AccountType.IBAN, AccountType.UBAN):
pass
class IbanUtils(object):
def account_type_in(self, account_type, *types_to_check):
for type in types_to_check:
if not isinstance(type, Variant):
log.warning('Unsafe usage')
return account_type in types_to_check
But it's not an option for me, because I have a lot of legacy code I cannot touch, but still need to report on.
I am defining models for my app and I need to a column named 'status' for various verification procedures. Here is a simplified user model.
class User
id(int)
name(str)
status(int) # 0- New 1-Active 2-Inactive 3-Reported 4-Deleted
I asked a fellow Python developer to review my code; and he suggested that I avoided 'magic numbers'. His solution is this:
class Choices:
#classmethod
def get_value(cls, key):
# get the string display if need to show
for k, v in cls.CHOICES:
if k == key:
return v
return ""
class UserStatusChoices(Choices):
NEW = 0
ACTIVE = 1
INACTIVE = 2
REPORTED = 3
DELETED = 4
CHOICES = (
(NEW, "NEW"),
(ACTIVE, "ACTIVE"),
(INACTIVE, "INACTIVE"),
(REPORTED, "REPORTED"),
(DELETED, "DELETED"),
)
Couldn't I use simple dictionaries instead? Does anyone see a good reason for 'class'y solution?
Building on Python Enum class (with tostring fromstring)
class Enum(object):
#classmethod
def tostring(cls, val):
for k,v in vars(cls).iteritems():
if v==val:
return k
#classmethod
def fromstring(cls, str):
return getattr(cls, str.upper(), None)
#classmethod
def build(cls, str):
for val, name in enumerate(str.split()):
setattr(cls, name, val)
class MyEnum(Enum):
VAL1, VAL2, VAL3 = range(3)
class YourEnum(Enum):
CAR, BOAT, TRUCK = range(3)
class MoreEnum(Enum):
pass
print MyEnum.fromstring('Val1')
print MyEnum.tostring(2)
print MyEnum.VAL1
print YourEnum.BOAT
print YourEnum.fromstring('TRUCK')
# Dodgy semantics for creating enums.
# Should really be
# MoreEnum = Enum.build("CIRCLE SQUARE")
MoreEnum.build("CIRCLE SQUARE")
print MoreEnum.CIRCLE
print MoreEnum.tostring(1)
print MoreEnum.tostring(MoreEnum.CIRCLE)
EDIT Added build class method so that a string could be used to build the enums.
Although there are probably better solutions out there.
I want to take an argument and create a class whose name is the argument itself.
For example, I take 'Int' as an argument and create a class whose name is 'Int',
that is my class would be like this.
class Int :
def __init__(self,param) :
self.value = 3
I am doing this by doing this.
def makeClass( x ) :
return eval( 'class %s :\n def __init__(self,param) :\n self.type = 3'%(x,))
and then calling
myClass = makeClass('Int')
myInt = myClass(3)
I am getting a syntax error for this. Please help.
eval is used for evaluating expressions, class is not an expression, it's a statment. Perhaps you want something like exec?
As a side note, what you're doing here could probably be done pretty easily with type, and then you sidestep all of the performance and security implications of using eval/exec.
def cls_init(self,param):
self.type = 3
Int = type("Int",(object,),{'__init__':cls_init})
# ^class name
# ^class bases -- inherit from object. It's a good idea :-)
# ^class dictionary. This is where you add methods or class attributes.
As requested, this works in Python 2.7 and Python 3.4, printing 3:
def makeClass(x):
exec('class %s:\n\tdef __init__(self,v):\n\t\tself.value = v' % x)
return eval('%s' % x)
myClass = makeClass('Int')
myInt = myClass(3)
print(myInt.value)
If you wish to add methods from the existing classes:
def makeClass(name):
parent = name.lower()
exec('class %s(%s):\n\tdef __init__(self,v):\n\t\tself.value = v' % (name, parent))
return eval('%s' % name)
Int = makeClass('Int')
myInt = Int(3)
Str = makeClass('Str')
myStr = Str(3)
print(myInt.value, myInt == 3, myInt == 5)
print(myStr.value, myStr == '3', myStr == 3)
Output:
3 True False
3 True False
Less typing with side effects:
def makeClass(name):
parent = name.lower()
exec('global %s\nclass %s(%s):\n\tdef __init__(self,v):\n\t\tself.value = v' % (name, name, parent))
makeClass('Int')
myInt = Int(3)
makeClass('Str')
myStr = Str(3)
Mgilson's type answer is probably preferred, though.