Skip to content
Advertisement

unexpected behavior with EnumMeta class

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.

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement