How do you look up an Enum member by name versus by value, and what does each raise?
answer
- The syntax picks which door you use
- Parentheses versus square brackets
- One raises ValueError, one KeyError
- member.value round-trips one way
- __members__ gets you a miss without an exception
basics
~10 sCalling the class looks up by value, as in Bin(2), and raises ValueError when no member holds that value. Subscripting looks up by name, as in Bin['STAGING'], and raises KeyError. Attribute access raises AttributeError.
solid answer
~40 sThe syntax picks the lookup. `Bin(2)` is the **value** door: it searches the value-to-member mapping the class built at definition time and raises `ValueError: 2 is not a valid Bin` on a miss — that is the deserialization direction. `Bin['STAGING']` is the **name** door: it searches the name-to-member mapping and raises `KeyError` on a miss, exactly like a dict; `Bin.STAGING` is the same lookup written statically, raising `AttributeError` instead. The round trip is symmetrical: `Bin(m.value) is m` and `Bin[m.name] is m`. There is no fallback between the two, so `Bin('STAGING')` is a *value* lookup for a string and raises `ValueError`. Value lookup compares with `==` and always returns the canonical member, so an alias's value resolves to the member it aliases; the name door and `__members__` are the ones that still see alias names.
code
python · 18 linesimport enum
class Bin(enum.Enum):
AISLE_A = 1
STAGING = 2
HOLD = 1 # alias for AISLE_A
print(Bin['AISLE_A'], Bin(2)) # by name, then by value
print(Bin(1).name) # a value lookup returns the canonical member
print(Bin.__members__['HOLD'] is Bin.AISLE_A)
for lookup in (lambda: Bin['NOPE'], lambda: Bin(7)):
try:
lookup()
except (KeyError, ValueError) as exc:
print(type(exc).__name__, exc)
print(Bin.__members__.get('NOPE')) # name lookup without an exceptiongo deeper
Remember the two forms and pair each with its error: parentheses look up by value and raise ValueError, square brackets look up by name and raise KeyError. Writing the wrong one is the single most common enum mistake.
Be ready to explain the round trip — member.value goes back through the call syntax, member.name through the subscript — that there is no fallback between the doors, and that value lookup returns the canonical member when aliases exist.
Demonstrate safe parsing of untrusted input: which door the stored representation implies, members.get over getattr for arbitrary strings, and a deliberate decision about whether an unknown code should raise or degrade.
Own the boundary contract: whether names or values are the persisted and exchanged representation, what a rename costs under each, and how that decision is kept symmetrical between the write path and the read path across services.
### Two lookups, two syntaxes, two exceptions An `enum.Enum` subclass answers to two different questions, and the syntax you write picks which one you are asking. **By value — call the class.** `Bin(2)` searches the value-to-member mapping the class built at definition time and returns the member holding that value. A miss raises `ValueError` with a message of the form `7 is not a valid Bin`. This is the deserialization direction: you have a number or a string that came out of a database row, a message body or a config file, and you want the member. **By name — subscript the class.** `Bin['STAGING']` searches the name-to-member mapping and returns the member with that name. A miss raises `KeyError`, carrying just the missing name, exactly as a mapping lookup does. This is the direction you use when a *name* was written down — the value of `member.name`, a value in an enum-typed column that stores names, a CLI argument spelled after the constant. **By name, statically — attribute access.** `Bin.STAGING` is the ordinary form you write in source. It is the same object as `Bin['STAGING']`; the difference is only that the name is baked into the source, and that a wrong one raises `AttributeError` rather than `KeyError`. Remembering which is which is easiest through the round trip: `member.value` goes back with the call syntax, `member.name` goes back with the subscript. ```python import enum class Bin(enum.Enum): AISLE_A = 1 STAGING = 2 assert Bin(Bin.STAGING.value) is Bin.STAGING # value goes back through the call assert Bin[Bin.STAGING.name] is Bin.STAGING # name goes back through the subscript ``` ### The details that catch people **Value lookup is by equality, not identity or type.** The class compares the argument against stored values with `==`, so an equal value of a different type still matches: on a class with `AISLE_A = 1`, `Bin(True)` returns `Bin.AISLE_A`, because `True == 1`. That is worth knowing when values arrive from a loosely-typed source. **Value lookup always returns the canonical member.** If a later name in the class body repeated an existing value, that name is an alias for the first member, and `Bin(1)` returns the first member regardless of how many names claim the value. Iteration over the class also skips alias names — `__members__` is the mapping that keeps them. **Name lookup does see aliases.** `Bin['HOLD']` succeeds for an alias, and `'HOLD'` appears in `Bin.__members__`, even though `HOLD` never appears in `list(Bin)`. **There is no fallback between the two.** `Bin('STAGING')` does not quietly try the name mapping; it is a value lookup for the string `'STAGING'`, finds no member whose value is that string, and raises `ValueError`. Likewise `Bin[2]` is a name lookup for a key that is not a string and raises `KeyError`. A great many bug reports are exactly this confusion. ### Parsing untrusted input For input you do not control, decide up front which lookup you mean and handle the miss explicitly. For values, wrap the call: ```python def to_bin(value, default=None): try: return Bin(value) except ValueError: return default ``` For names, prefer the mapping over an exception: `Bin.__members__.get(text)` returns `None` for an unknown name and costs nothing. Reach for `getattr(Bin, text, None)` only with care — `getattr` will happily return any class attribute, including a helper method defined on the enum, so a hostile or sloppy string can hand you something that is not a member at all. `__members__` contains members and nothing else, which is precisely why it is the safer door for arbitrary strings. ### Membership tests `x in Bin` is a third question again: it asks whether `x` is a member of this enum, and since **3.12** it also accepts a plain *value* and returns `True` or `False` rather than raising. Before 3.12, testing a non-member raised `TypeError`. It has never matched *names*: on a class with `STAGING = 2`, `2 in Bin` is `True` while `'STAGING' in Bin` is `False`. So `in` is a value-side check, not a substitute for looking a name up in `__members__`. ### Choosing what to persist Because the two lookups are separate doors, decide deliberately which side you store. Names are stable against value renumbering and read well in a log; values are stable against a constant being renamed and are what an integer or string column usually holds. Whichever you pick, keep the read path symmetrical with the write path — a system that writes `member.name` and reads with the call syntax fails on the first record, and the `ValueError` it raises names the value, not the mistake.
- How do you turn an untrusted name string into a member without a try/except?Use `Cls.__members__.get(text)`, which returns `None` for an unknown name. Prefer it to `getattr(Cls, text, None)`: `getattr` returns any class attribute, including helper methods defined on the enum, so a hostile or sloppy string can hand back something that is not a member. `__members__` contains members and nothing else.
- What does a value lookup return when that value belongs to an alias?The canonical member — the first name in the class body that claimed the value. Aliases have no separate object, so `Cls(1)` returns the same member however many names share the value 1, and `member.name` reports the canonical spelling. Only the name door and `__members__` still see the alias name.
- Does value lookup require the exact type of the stored value?No. The class compares candidates with `==`, so an equal value of another type still matches: on an enum with `AISLE_A = 1`, calling the class with `True` returns that member, because `True == 1`. Worth knowing when values arrive from a loosely typed source; validate the type yourself if that match would be wrong.
A warehouse has two indexes for the same shelf: one keyed by the label painted on it and one keyed by its stock number. Asking the wrong index for the wrong key does not quietly try the other one.
saying these in an interview costs you the question
- Thinks calling the class with a name string looks a name up
- Expects KeyError from a failed value lookup
- Believes lookup falls back from value to name
- Thinks __members__ leaves alias names out
- Uses getattr on the class for arbitrary user strings
- Assumes a membership test matches member names