Enum dict comprehension in 3.8.9/3.9.6 inconsistent with __members__ mapping

I’ve noticed in Python 3.8.9 and 3.9.6 that a dict comprehension of an Enum where two keys share the same value produces results which seem to be incorrect, and inconsistent with the same comprehension on the Enum __members__ mapping. Example below in iPython 3. 3.9.6:

In [1]: from enum import Enum

In [2]: E = Enum('E', {'a': 1, 'b': 1, 'c': 3})

In [3]: E.a
Out[3]: <E.a: 1>

In [4]: E.b
Out[4]: <E.a: 1>

In [5]: E.c
Out[5]: <E.c: 3>

In [6]: E.a.value
Out[6]: 1

In [7]: E.b.value
Out[7]: 1

In [8]: E.c.value
Out[8]: 3

In [9]: {k.name: k.value for k in E}
Out[9]: {'a': 1, 'c': 3}

In [10]: {k: v.value for k, v in E.__members__.items()}
Out[10]: {'a': 1, 'b': 1, 'c': 3}

It seems that in the first dict comprehension keys which share the same values are deduplicated. Why is this? Is this a bug in enum.Enum?

I think your E.b is considered an alias. The iteration ignores aliases,

From the docs

  • can be iterated over to return its canonical (i.e. non-alias) members in definition order

since it runs over E._member_names_, instead of __members__,

def __iter__(cls):
        Returns members in definition order.
        return (cls._member_map_[name] for name in cls._member_names_)

which is equal to ['a', 'c'] in your case.

Thanks, looking at E.__iter__ it is now clear why iterating over E only produces 'a', 'b' , but the aliasing aspect isn’t clear from the docs. I was treating enums as dict-like with unique keys and “constant” values, but the key uniqueness would appear to be a property of the member enums, and not the names.

The uniqueness of values is the first property mentioned in the documentation.

See also this tutorial about aliases.