How to convert int to Enum in python? - python

Using the new Enum feature (via backport enum34) with python 2.7.6.
Given the following definition, how can I convert an int to the corresponding Enum value?
from enum import Enum
class Fruit(Enum):
Apple = 4
Orange = 5
Pear = 6
I know I can hand craft a series of if-statements to do the conversion but is there an easy pythonic way to convert? Basically, I'd like a function ConvertIntToFruit(int) that returns an enum value.
My use case is I have a csv file of records where I'm reading each record into an object. One of the file fields is an integer field that represents an enumeration. As I'm populating the object I'd like to convert that integer field from the file into the corresponding Enum value in the object.

You 'call' the Enum class:
Fruit(5)
to turn 5 into Fruit.Orange:
>>> from enum import Enum
>>> class Fruit(Enum):
... Apple = 4
... Orange = 5
... Pear = 6
...
>>> Fruit(5)
<Fruit.Orange: 5>
From the Programmatic access to enumeration members and their attributes section of the documentation:
Sometimes it’s useful to access members in enumerations
programmatically (i.e. situations where Color.red won’t do because the
exact color is not known at program-writing time). Enum allows such
access:
>>> Color(1)
<Color.red: 1>
>>> Color(3)
<Color.blue: 3>
In a related note: to map a string value containing the name of an enum member, use subscription:
>>> s = 'Apple'
>>> Fruit[s]
<Fruit.Apple: 4>

I think it is in simple words is to convert the int value into Enum by calling EnumType(int_value), after that access the name of the Enum object:
my_fruit_from_int = Fruit(5) #convert to int
fruit_name = my_fruit_from_int.name #get the name
print(fruit_name) #Orange will be printed here
Or as a function:
def convert_int_to_fruit(int_value):
try:
my_fruit_from_int = Fruit(int_value)
return my_fruit_from_int.name
except:
return None

class Status(IntEnum):
UPLOADED = 1
DOWNLOADED = 5
SEGMENTED = 10
DIRECTED = 15
READYTODEEP = 20
to get the enum;
statusId = 5
Status(statusId)
to get the enum's string value;
statusId = 5
print(Status(statusId).name)

I wanted something similar so that I could access either part of the value pair from a single reference. The vanilla version:
#!/usr/bin/env python3
from enum import IntEnum
class EnumDemo(IntEnum):
ENUM_ZERO = 0
ENUM_ONE = 1
ENUM_TWO = 2
ENUM_THREE = 3
ENUM_INVALID = 4
#endclass.
print('Passes')
print('1) %d'%(EnumDemo['ENUM_TWO']))
print('2) %s'%(EnumDemo['ENUM_TWO']))
print('3) %s'%(EnumDemo.ENUM_TWO.name))
print('4) %d'%(EnumDemo.ENUM_TWO))
print()
print('Fails')
print('1) %d'%(EnumDemo.ENUM_TWOa))
The failure throws an exception as would be expected.
A more robust version:
#!/usr/bin/env python3
class EnumDemo():
enumeration = (
'ENUM_ZERO', # 0.
'ENUM_ONE', # 1.
'ENUM_TWO', # 2.
'ENUM_THREE', # 3.
'ENUM_INVALID' # 4.
)
def name(self, val):
try:
name = self.enumeration[val]
except IndexError:
# Always return last tuple.
name = self.enumeration[len(self.enumeration) - 1]
return name
def number(self, val):
try:
index = self.enumeration.index(val)
except (TypeError, ValueError):
# Always return last tuple.
index = (len(self.enumeration) - 1)
return index
#endclass.
print('Passes')
print('1) %d'%(EnumDemo().number('ENUM_TWO')))
print('2) %s'%(EnumDemo().number('ENUM_TWO')))
print('3) %s'%(EnumDemo().name(1)))
print('4) %s'%(EnumDemo().enumeration[1]))
print()
print('Fails')
print('1) %d'%(EnumDemo().number('ENUM_THREEa')))
print('2) %s'%(EnumDemo().number('ENUM_THREEa')))
print('3) %s'%(EnumDemo().name(11)))
print('4) %s'%(EnumDemo().enumeration[-1]))
When not used correctly this avoids creating an exception and, instead, passes back a fault indication. A more Pythonic way to do this would be to pass back "None" but my particular application uses the text directly.

Related

Pytest: assert a list contains n instances of a class / object

#dataclass
class Component:
name: str
class Assembly:
def __init__():
names = ["Able", "Baker", "Charlie"]
self.components = [Component(name) for name in names]
def test_assembly():
"""Assembly should be initialised with three Components"""
assembly = Assembly()
assert assembly.components == [Component] * 3 # doesn't work
The assert doesn't work because it's comparing a list of class instances (assembly.components) to a list of class types. Is there a good way to write this test?
You could iterate through the list and use isinstance():
def count_instance(lst, cls):
count = 0
for i in lst:
if isinstance(i, cls):
count += 1
return count
assert count_instance(assembly.components, Component) == 3
You can convert all of the elements in assembly.components into types with map() and then you can convert the map into a list and use the list.count function to see how many times the type appears in the list. Something like this:
assert list(map(type,assembly.components)).count(Component) == 3
if each item in assembly.components is already a type, you can just make sure its a list and then use the list.count function immedietly like this:
assert list(assembly.components).count(Component) == 3
Edit:
Also should you find yourself iterating over anything and want to compare the number of times a pattern appear, i believe its far better to use an int variable and increment it for each pattern you find rather than what you were doing which is creating entirely new lists and comparing them which in large quantities will slow down quite quick. (The increment method is how i bleieve the .count function works so where possible try to just use that.)
assert [type(comp) for comp in assembly.components] == [Component] * 3
This does the job - but I suspect there may be a better solution?
You could assert the class types on each element in the list individually, however that's a relatively expensive operation, assuming you already know that each element in components will be a Component type.
Instead, you could just check the length of the list, as shown below; this assumes that you don't try to add an integer value, for example, to components outside of the class constructor. Though you could also assert that each element in the list is a Component type rather easily, using the builtin map operator and then unpacking the iterator result into a list, which is also shown as well.
Note: attributes annotated as ClassVar are assumed to be class or static attributes only, and so will be ignored by the dataclass decorator when building a list of dataclass fields.
from dataclasses import dataclass
from typing import ClassVar, List
#dataclass
class Component:
name: str
#dataclass
class Assembly:
names: ClassVar[List[str]] = ["Able", "Baker", "Charlie"]
components: List[Component]
def __init__(self):
self.components = [Component(name) for name in self.names]
def main():
"""Assembly should be initialised with three Components"""
assembly = Assembly()
assert len(assembly.components) == 3 # works
# bonus: check each element is a Component object also
assert assembly.components == [*map(Component, assembly.names)]
if __name__ == '__main__':
main()

supermarket dataclasses

from dataclasses import dataclass
from typing import Union
#dataclass
class OtherStock:
name:str = "gold"
units:int = 7
price_per_unit : float = 150000
#dataclass
class FoodStock:
name:str = "bread"
units:int = 2
price_per_unit: float = 700
expiration_date: str = "2021-02-16"
stock = Union[OtherStock,FoodStock]
def is_expired(inventory:stock,date:str)->bool:
if type(inventory) == OtherStock:
return False
if date == inventory.expiration_date:
return True
else:
return False
print(is_expired("bread","2021-02-16"))
print(is_expired("gold","2021-02-16"))
The code should compare both attributes in date and expiration_date and should return True if the product is expired. OtherStock can't expire so it returns false every time. If I try to run it I get an AttributeError: 'str' object has no attribute 'expiration_date'.
Your code passes strings to is_expired, not objects. "bread" is a string.
Maybe you want print(is_expired(FoodStock(), "2021-02-16"))?
Like you said, the problem is with inventory.expiration_date, and like the error says, inventory is a str. And the first arguments you pass ("bread" and "gold") are indeed strings.
You'll need to create your stock objects first. Python won't do it for you. However, according to documentation, Python automatically creates initialisers for us. That's the FoodStock(...) and OtherStock(...) calls you see below.
today = "2021-02-16"
print(is_expired(FoodStock(name="bread", expiration_date="2021-02-12"), today))
print(is_expired(OtherStock(name="gold"), today))
Here, we used keyword arguments to specify individual fields.
You may also want to consider checking date >= inventory.expiration_date instead of ==.

Default or invalid value for enumerator

In Python, I've been creating enums using the enum module. Usually with the int-version to allow conversion:
from enum import IntEnum
class Level(IntEnum):
DEFAULTS = 0
PROJECT = 1
MASTER = 2
COLLECT = 3
OBJECT = 4
I would like to provide some type of invalid or undefined value for variables of this type. For example, if an object is initialized and its level is currently unknown, I would like to create it by doing something like self.level = Level.UNKNOWN or perhaps Level.INVALID or Level.NONE. I usually set the internal value of these special values to -1.
The type of problems I keep running into is that adding any special values like this will break iteration and len() calls. Such as if I wanted to generate some list to hold each level type list = [x] * len(Level), it would add these extra values to the list length, unless I manually subtract 1 from it. Or if I iterated the level types for lvl in Level:, I would have to manually skip over these special values.
So I'm wondering if there is any clever way to fix this problem? Is it pointless to even create an invalid value like this in Python? Should I just be using something like the global None instead? Or is there some way to define the invalid representation of the enumerator so that it doesn't get included in iteration or length logic?
The answer to this problem is similar to the one for Adding NONE and ALL to Flag Enums (feel free to look there for an in-depth explanation; NB: that answer uses a class-type decorator, while the below is a function-type decorator).
def add_invalid(enumeration):
"""
add INVALID psuedo-member to enumeration with value of -1
"""
#
member = int.__new__(enumeration, -1)
member._name_ = 'INVALID'
member._value_ = -1
enumeration._member_map_['INVALID'] = member
enumeration._value2member_map_[-1] = member
return enumeration
Which would look like
#add_invalid
class Level(IntEnum):
DEFAULTS = 0
PROJECT = 1
MASTER = 2
COLLECT = 3
OBJECT = 4
and in use:
>>> list(Level)
[<Level.DEFAULTS: 0>, <Level.PROJECT: 1>, <Level.MASTER: 2>, <Level.COLLECT: 3>, <Level.OBJECT: 4>]
>>> type(Level.INVALID)
<enum 'Level'>
>>> Level.INVALID
<Level.INVALID: -1>
>>> Level(-1)
<Level.INVALID: -1>
>>> Level['INVALID']
<Level.INVALID: -1>
There are a couple caveats to this method:
it is using internal enum structures that may change in the future
INVALID, while not showing up normally, is otherwise an Enum member (so cannot be changed, deleted, etc.)
If you don't want to use internal structures, and/or you don't need INVALID to actually be an Enum member, you can instead use the Constant class found here:
class Constant:
def __init__(self, value):
self.value = value
def __get__(self, *args):
return self.value
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.value)
Which would look like
class Level(IntEnum):
#
DEFAULTS = 0
PROJECT = 1
MASTER = 2
COLLECT = 3
OBJECT = 4
#
INVALID = Constant(-1)
and in use:
>>> Level.INVALID
-1
>>> type(Level.INVALID)
<class 'int'>
>>> list(Level)
[<Level.DEFAULTS: 0>, <Level.PROJECT: 1>, <Level.MASTER: 2>, <Level.COLLECT: 3>, <Level.OBJECT: 4>]
The downside to using a custom descriptor is that it can be changed on the class; you can get around that by using aenum1 and its built-in constant class (NB: lower-case):
from aenum import IntEnum, constant
class Level(IntEnum):
#
DEFAULTS = 0
PROJECT = 1
MASTER = 2
COLLECT = 3
OBJECT = 4
#
INVALID = constant(-1)
and in use:
>>> Level.INVALID
-1
>>> Level.INVALID = None
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/ethan/.local/lib/python3.6/site-packages/aenum/__init__.py", line 2128, in __setattr__
'%s: cannot rebind constant %r' % (cls.__name__, name),
AttributeError: Level: cannot rebind constant 'INVALID'
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
Idiomatically speaking, when you use an enumerator it is because you know without a doubt everything will fall into one of the enumerated categories. Having a catch-all "other" or "none" category is common.
If the level of an item isn't known at the time of creation, then you can instantiate all objects with the "unknown" level unless you supply it another level.
Is there a particular reason you are treating these internally with a -1 value? Are these levels erroneous, or are they having an "unknown" level valid?

Cast binary to enum in python for redis conversion

I am storing an enum in redis. When I load it, the value is in binary. Howe can I cast it to be a python enum?
Example code:
class Position(Enum):
LEFT = 10
RIGHT = 11
current_position = Position.LEFT
r.set('current_position', Position.LEFT)
loaded_current_position = r.get('current_position_side')
print(current_position) # Position.LEFT
print(loaded_current_position) # b'Position.LEFT'
In this example, I'd like to get loaded_current_position to equal Position.LEFT not b'Position.LEFT'
What's being stored is the name of the Enum member. There may be a redis specific answer, but in general you can mix your Enum with another data type, such as int, and then cast it back to an Enum upon retrieval; something like:
class Position(int, Enum):
LEFT = 10
RIGHT = 11
current_position = Position.LEFT
r.set('current_position', Position.LEFT)
loaded_current_position = Position(r.get('current_position_side'))
print(current_position) # Position.LEFT
print(loaded_current_position) # Position.LEFT
Note: for the above to work, redis must save and return an int

How to get back name of the enum element?

I have an enum defined like this:
def enum(**enums):
return type('Enum', (), enums)
Status = enum(
STATUS_OK=0,
STATUS_ERR_NULL_POINTER=1,
STATUS_ERR_INVALID_PARAMETER=2)
I have a function that returns status as Status enum.
How can I get the name of the enum value, and not just value?
>>> cur_status = get_Status()
>>> print(cur_status)
1
I would like to get STATUS_ERR_NULL_POINTER, instead of 1
You'd have to loop through the class attributes to find the matching name:
name = next(name for name, value in vars(Status).items() if value == 1)
The generator expression loops over the attributes and their values (taken from the dictionary produced by the vars() function) then returns the first one that matches the value 1.
Enumerations are better modelled by the enum library, available in Python 3.4 or as a backport for earlier versions:
from enum import Enum
class Status(Enum):
STATUS_OK = 0
STATUS_ERR_NULL_POINTER = 1
STATUS_ERR_INVALID_PARAMETER = 2
giving you access to the name and value:
name = Status(1).name # gives 'STATUS_ERR_NULL_POINTER'
value = Status.STATUS_ERR_NULL_POINTER.value # gives 1
2021 update:
These answers are out of date. Using Python's standard Enum class,
cur_status.name
will return the name. (STATUS_ERR_NULL_POINTER)
To look up the enum knowing the name:
s = Status['STATUS_ERR_NULL_POINTER']
Not sure which python version it was introduced, but the hidden attribute _value2member_map_ gives you what you want.
class Status(Enum):
STATUS_OK=0
STATUS_ERR_NULL_POINTER=1
STATUS_ERR_INVALID_PARAMETER=2
str(Status._value2member_map_[1])
Out:
'Status.STATUS_ERR_NULL_POINTER'
You don't need to loop through the Enum class but just access _member_map_.
>>> Status._member_map_['STATUS_OK']
<Status.STATUS_OK: 0>
For some reason, most of the methods above did not work for me. All methods return the Enum type as an integer. I'm working with Python 3.7.
In my solution, I defined class function to handle this. It's not purely pythonic, but worked well enough for my case.
from enum import Enum
class Status(Enum):
STATUS_OK = 0
STATUS_ERR_NULL_POINTER = 1
STATUS_ERR_INVALID_PARAMETER = 2
#classmethod
def name(cls,val):
return { v:k for k,v in dict(vars(cls)).items() if isinstance(v,int)}.get(val,None)
# test it
stat = Status.STATUS_OK
print(Status.name(stat))
Prints: 'STATUS_OK'
It may seem obvious that we asked for the status after giving it the status, but in my case, this is set programmatically elsewhere

Categories

Resources