In a recent post about Python enums I mentioned the _missing_ method, that gets executed when accessing an enum member by value if that value is missing.
from enum import Enum
class Color(Enum):
RED = "red"
BLUE = "blue"
@classmethod
def _missing_(cls, value):
value = value.lower()
print(f"missing value: {value}")
for member in cls:
if member.value == value:
return member
return None
print(Color.RED is Color("red"))
# True
print(Color.RED is Color("rEd"))
# missing value: rEd
# True
We know that we can also get an enum member by name, using the [] syntax (subscripted access or key/name access), like color = Color["RED"], and well, it feels to me that it would be nice to have this kind missing functionality for that kind of access (let's call it 'missing_key'). I mean, defining a sort of missing_key method in our enum classes that would get invoked if the key/name used does not exist. And how can we hook this behaviour into the subscripted access to the enum? At the end of this post I talked about those __dunder_methods__ that work as behavioral hooks. We have hooks for invokation, attribute getting, attibute setting... and also one, __getitem__ for subscripted access. For this case, we'll be using the subscripted access in the class, so we have to implement it in a metaclass. The Enum class has EnumMeta as its metaclass, so we'll need our new metaclass to inherit from EnumMeta. When creating an Enum empowered with this "missing_key" functionality, it'll have to use our new metaclass, and inherit from Enum (or related child classes like IntEnum, StrEnum...).
`EnumMeta` implements the class-creation machinery (turning attributes into members, building `__members__` and `_value2member_map_`), but it relies on the base class (`Enum`) to provide the instance API and the initialization/constructor expectations used when creating each member. Without inheriting from `Enum`, those assumptions break and class creation fails or yields an object that isn't a proper enum.
So we define this metaclass, that when accessing a missing key will invoke a _missing_key method (if it exists) in the enum class.
from enum import EnumMeta, Enum
class MissingKeyEnum(EnumMeta):
def __getitem__(cls, name):
try:
return super().__getitem__(name)
except KeyError:
print(f"Name '{name}' is not a valid member of {cls.__name__}")
# Call a dedicated missing-name handler if present
if hasattr(cls, "_missing_key"):
return cls._missing_key(name)
return None
An now we define a class that leverages the metaclass to allow case insensitive subscripted access:
class Query(Enum, metaclass=MissingKeyEnum):
SELECT = "select"
INSERT = "insert"
UPDATE = "update"
DELETE = "delete"
@classmethod
def _missing_key(cls, name):
# This method is called when a name is not found in the enum
# You can customize the behavior here, for example, allowing case insensitive access
return getattr(cls, name.upper(), None) # Return the enum member if it exists, otherwise return None
query1 = Query["INSERT"] # This will return the enum member Query.INSERT
query2 = Query["Insert"] # This will go through _missing_key and return Query.INSERT
print(query1 is query2) # This will print True, as both refer to the same enum member
query3 = Query["insssseeerrtt"] # This will go through _missing_key and return None
print(f"query3: {query3}") # This will print None, as the name is not valid
Just to close this post I'll mention an interesting article that discusses inheriting from Enum vs using a new metaclass that inherits from EnumMeta.
No comments:
Post a Comment