I defined an IntEnum
for some weather codes and provided a metaclass.
from enum import IntEnum, EnumMeta class WXCodeMeta(EnumMeta): def __iter__(self: type[IntEnum]) -> Iterator[tuple[str, int]]: for member in super().__iter__(): yield member.name, member.value def names(self: type[IntEnum]): return tuple(member.name for member in super().__iter__()) def values(self: type[IntEnum]): return tuple(member.value for member in super().__iter__()) class WXCodes(IntEnum, metaclass=WXCodeMeta): RA = 1 TS = 2 BR = 3 SN = 4 FG = 5
as show above it functions as expected when passed into a dict
.
>>> dict(WXCodes) {'RA': 1, 'TS': 2, 'BR': 3, 'SN': 4, 'FG': 5}
but if I change the method name of names
to keys
like …
class WXCodeMeta(EnumMeta): ... def keys(self: type[IntEnum]): return tuple(member.name for member in super().__iter__())
I get an unexpected result
>>> dict(WXCodes) {'RA': <WXCodes.RA: 1>, 'TS': <WXCodes.TS: 2>, 'BR': <WXCodes.BR: 3>, 'SN': <WXCodes.SN: 4>, 'FG': <WXCodes.FG: 5>}
It was my understanding that dict
calls the __iter__
method under the hood to create a __new__
dict
. Why does the keys
method name change the behavior?
Advertisement
Answer
When using dict
to construct dictionary objects, it will create an empty dictionary first. If there are arguments, the arguments will be passed to its update
method. It’s not convenient to directly show the C code of dict.update
, so the MutableMapping.update
method in _collections_abc.py
is provided here, from which we can see the update idea of dict.update
:
class MutableMapping(Mapping): ... def update(self, other=(), /, **kwds): '''...''' if isinstance(other, Mapping): for key in other: self[key] = other[key] elif hasattr(other, "keys"): for key in other.keys(): self[key] = other[key] else: for key, value in other: self[key] = value for key, value in kwds.items(): self[key] = value ...
When the other
passed in is not a mapping, MutableMapping
will first check whether the other
has keys
method. If so, it will try to iterate and get the corresponding value to update, rather than directly iterate over the other
object. It happens that EnumMeta.__getitem__
method gets the enumeration object itself according to its name, so you get a dictionary with name as the key and enumeration object as the value.