5

I've inherited code that looks something like this.

class Clients(IntEnum):
    ALICE = 1
    BOB = 2
    PETER = 3
    CHERYL = 4
    LARRY = 5

if client_id == 1:
    client_info = find_info(Clients.ALICE.value)
elif client_id == 2:
    client_info = find_info(Clients.BOB.value)
elif client_id == 3:
    client_info = find_info(Clients.PETER.value)
elif client_id == 4:
    client_info = find_info(Clients.CHERYL.value)
elif client_id == 5:
    client_info = find_info(Clients.LARRY.value)
else:
    raise Exception('Unknown client_id.')

Not having much experience with Python enum, I have a burning desire to simplify this into something like this (pseudocode):

if client_id in dict(Clients).keys():
    client_info = find_info(client_id)
else:
    raise Exception('Unknown client_id.')

I've tried Clients.__members__ and Clients.__dict__, but they don't quite behave as I'd expect, returning something called a mappingproxy.

I can do 'ALICE' in Clients.__members__, but is there an equivalent for the values? If not, is there a more elegant way to write this bit of code?

3
  • 1
    Possible duplicate of How to convert int to Enum in python? Commented Jan 10, 2019 at 10:34
  • 2
    Will client_id always be the same as the client's value? If so, you don't need a mapping at all, as enums allow access by value. Commented Jan 10, 2019 at 10:50
  • Maybe I should edit my question a bit. I've realised I can do Clients(client_id).name I guess my question is primarily how to validate the value. Commented Jan 10, 2019 at 10:55

4 Answers 4

9

You can store only values in a plain tuple (or list) and use the fact that enums allow access by value:

values = tuple(item.value for item in Clients)

if client_id in values:
    # do something with Clients(client_id)
else:
    # print message or raise exception

Or you can map values to enum members using a dictionary:

mapping = dict((item.value, item) for item in Clients)

if client_id in mapping:
    # do something with mapping[client_id]
else:
    # print message or raise exception
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks - ended up using a list [item.value for item in Clients]
3
try:
    client_info = find_info(Clients(client_id))
except ValueError:
    # customize error message
    raise Exception('Unknown client_id.')

3 Comments

I figure you're a good person to ask: in general, is there any Pythonic preference to an if...else... over try...except... (or vice versa)?
@JoshFriedlander: If an exception could be raised, and I wan't to fix it myself or change the exception in some way, then I use try/except; if/else is for testing conditions which either won't raise exceptions or I don't care about catching them; likewise, just doing it if I don't care about catching anything -- so the above would be client_info = find_info(Clients(client_id)) with no try/except and no if/else, and if a wrong value was specified then ValueError('... is not a valid Clients') would be raised.
1

Here is how you can validate values with helper functions:

For the Enums:

class Customers(IntEnum):
    ABC = 1
    FOO = 2
    JDI = 3

class Clients(IntEnum):
    ALICE = 1
    BOB = 2
    PETER = 3

Implement helper functions:

from enum import IntEnum, EnumMeta
def isEnumClass(o) -> bool:
    return type(o) == EnumMeta

def isEnumMember(o) -> bool:
    return type(o).__class__ == EnumMeta

def enumHasMember(en, o, int_val:bool=False) -> bool:
    assert isEnumClass(en),\
        "param en is not an Enum class. Got %s!" \
            % ('(none)' if en is None else type(o).__name__)
    if type(o) == int:
        if not int_val: return False
        return len([m.value for m in en if m.value == o]) == 1
    else:
        return not o is None and o.__class__ == en

Usage:


print("1: %s" % enumHasMember(Customers, Customers.ABC))
print("2: %s" % enumHasMember(Customers, 1, int_val=False))
print("3: %s" % enumHasMember(Customers, 1, int_val=True))
print("4: %s" % enumHasMember(Customers, 4, int_val=True))
print("5: %s" % enumHasMember(Customers, Clients.ALICE))

> 1: True
> 2: False
> 3: True
> 4: False
> 5: False

Alternatively, if you control all of the code, you can create create a classmethod for a custom IntEnu class:

from enum import IntEnum, EnumMeta
class MyIntEnum(IntEnum):
    @classmethod
    def hasMember(cls, o, strict:bool=True) -> bool:
        if type(o) == int:
            if strict: return False
            return len([m.value for m in cls if m.value == o]) == 1
        else:
            return not o is None and o.__class__ == cls


class Clients(MyIntEnum):
    ALICE = 1
    BOB = 2
    PETER = 3
    CHERYL = 4
    LARRY = 5
    
class Customers(MyIntEnum):
    ABC = 1
    FOO = 2
    JDI = 3

Usage:

print("1: %s" % Customers.hasMember(Customers.ABC))
print("2: %s" % Customers.hasMember(1))
print("3: %s" % Customers.hasMember(1, strict=False))
print("4: %s" % Customers.hasMember(4, strict=False))
print("5: %s" % Customers.hasMember(Clients.ALICE))

> 1: True
> 2: False
> 3: True
> 4: False
> 5: False

Another handy class method would be validate method for a one line hard assertion:

class MyIntEnum(IntEnum):
        ...
    @classmethod
    def validate(cls, alias:str, o, strict:bool=True):
        assertNotBlank('alias', alias)
        assert cls.hasMember(o, strict),\
            f"Argument '{alias}' is not a member of MyIntEnum {cls.__module__}.{type(cls).__name__}. Got: {dump(o, True)}"
        
        return o

Please let me know if there is any issue with the above design. Just started researching this myself.

Comments

0

I did something like this:

class Clients(IntEnum):
    ALICE = 1
    BOB = 2
    PETER = 3
    CHERYL = 4
    LARRY = 5

    @classmethod
    def values(self):       
        return [_.value for _ in list(self)]

    @classmethod
    def has(self, value):
        return value in self.values()


2 in Clients.values()  # True
22 in Clients.values() # False

Clients.has(2)  # True
Clients.has(22) # False

I usually implement both, because sometimes it makes sense to me in the code to say if Clients.has(id) and sometimes ( in forms for examples ) I want to test if the value given actually exists and provide an error accordingly so I say: if form_value in Clients.values()

So both do the same thing, just when you read the code afterwards it makes sense in term of context ...

1 Comment

Regardless of the subject, just naming convension but class ref should be named as cls not self.

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.