68

I just discovered the existence of an Enum base class in python and I'm trying to imagine how it could be useful to me.

Let's say I define a traffic light status:

from enum import Enum, auto

class Signal(Enum):
    red = auto()
    green = auto()
    orange = auto()

Let's say I receive information from some subsystem in my program, in the form of a string representing a colour name, for instance brain_detected_colour = "red".

How do I compare this string to my traffic light signals?

Obviously, brain_detected_colour is Signal.red is False, because Signal.red is not a string.

Signal(brain_detected_colour) is Signal.red fails with ValueError: 'red' is not a valid Signal.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
bli
  • 7,549
  • 7
  • 48
  • 94

4 Answers4

104

One does not create an instance of an Enum. The Signal(foo) syntax is used to access Enum members by value, which are not intended to be used when they are auto().

However one can use a string to access Enum members like one would access a value in a dict, using square brackets:

Signal[brain_detected_colour] is Signal.red

Another possibility would be to compare the string to the name of an Enum member:

# Bad practice:
brain_detected_colour is Signal.red.name

But here, we are not testing identity between Enum members, but comparing strings, so it is better practice to use an equality test:

# Better practice:
brain_detected_colour == Signal.red.name

(The identity comparison between strings worked thanks to string interning, which is better not to be relied upon. Thanks @mwchase and @Chris_Rands for making me aware of that.)

Yet another possibility would be to explicitly set the member values as their names when creating the Enum:

class Signal(Enum):
    red = "red"
    green = "green"
    orange = "orange"

(See this answer for a method to have this automated.)

Then, Signal(brain_detected_colour) is Signal.red would be valid.

bli
  • 7,549
  • 7
  • 48
  • 94
  • 18
    Using `is` in `brain_detected_colour is Signal.red.name` is risky; it'd be better to use `==`. – mwchase Jun 27 '17 at 13:39
  • 2
    @mwchase Could you explain why, so that I can edit my answer and add an explanation? – bli Jun 27 '17 at 13:41
  • 11
    You're relying on string interning, murky implementation details http://guilload.com/python-string-interning/, never use `is` unless you actually need to compare the identity of the objects – Chris_Rands Jun 27 '17 at 13:43
  • 2
    Solved my problem. Though I could not use `Signal(brain_detected_colour) is Signal.red` (mentioned at the end of the answer, for when an enum is built with string values)... `Signal[brain_detected_colour] == Signal.red` does work however. Note that I took into account the comment saying that using `==`for the comparison is less risky. – Sander Vanden Hautte Feb 21 '18 at 14:42
  • Yes, i had the same issue when comparing an enum value with a string, although they had the same string value, with "is" it did not work, using == solved my problems – MissSergeivna Jan 10 '21 at 16:05
  • To save other people searching, note that `a is b` is syntactic sugar for `id(a) == id(b)` – Hugh Perkins Jan 09 '23 at 09:49
75

A better practice is to inherit Signal from str:

class Signal(str, Enum):
    red = 'red'
    green = 'green'
    orange = 'orange'

brain_detected_colour = 'red'
brain_detected_colour == Signal.red  # direct comparison
Mushif Ali Nawaz
  • 3,707
  • 3
  • 18
  • 31
  • 2
    Can you explain in the answer, why this is a better practice? – Martin Grey Jul 24 '20 at 10:07
  • 10
    Just in case you are still interested on knowing why is better to use the mixin str https://docs.python.org/3/library/enum.html#others – Jorge Limas Aug 25 '20 at 16:59
  • 2
    This worked for me. @MartinGrey that might be because you comparing a `string` type with and `enum` type. I was having probelm comparing string with enum when `str` was not present in enum class. (assigning string values was not enough, i guess.) – omer Feb 09 '21 at 11:37
  • Shouldn't you use `.value` (e.g. `Signal.red.value`) when comparing? – Olimjon Ibragimov Jun 09 '22 at 23:38
  • 4
    @OlimjonIbragimov You need to do that when you're using a normal `Enum`. But when you mix-in `str` for example: `class Signal (str, Enum):`, then you don't need to do that. – Mushif Ali Nawaz Jun 10 '22 at 15:52
34

It is possible to have auto() return the name of the enum member as its value (which is in the auto section of the docs1:

class AutoName(Enum):
    def _generate_next_value_(name, start, count, last_values):
        return name

class Ordinal(AutoName):
    NORTH = auto()
    SOUTH = auto()
    EAST = auto()
    WEST = auto()

and in use:

>>> list(Ordinal)
[<Ordinal.NORTH: 'NORTH'>, <Ordinal.SOUTH: 'SOUTH'>, <Ordinal.EAST: 'EAST'>, <Ordinal.WEST: 'WEST'>]

1 This requires version Python 3.6, or aenum 2.02 (aenum works with Pythons as old as 2.7).

2 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

Ethan Furman
  • 63,992
  • 20
  • 159
  • 237
  • 5
    Unfortunately though the following assert fails: `assert Ordinal.NORTH == "NORTH"` so you can't directly compare to a string. Edit: oh, this can work by using `class Ordinal(str, AutoName)`. – tiho Jun 30 '20 at 21:01
  • Would you mind editing the code so that it's copy-pastable? – Guimoute Dec 03 '20 at 20:51
2
class Signal(Enum):
    red = auto()
    green = auto()
    orange = auto()

    def equals(self, string):
       return self.name == string

brain_detected_colour = "red"

if Signal.red.equals(brain_detected_colour):
   #something awesome
tsz662
  • 164
  • 1
  • 12