1

I'm nearly convinced this isn't possible, but here goes: I have class a(enum.Enum) where I'd like to override the __contains__ which is inherited from metaclass=enum.EnumMeta Is it possible to override the EnumMeta::__contains__ from my class a?

I guess I need to duplicate everything to achieve this?

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • In a way, and then again.. I'd prefer not to have to replace the base class Enum as is proposed, which requires me to duplicate the entire content of Enum class (not desired). I found a different way which allows me to keep the standard classes intact, so that if they are changed in the future I'll still get those changes, and while its not classic inheritance the benefits outweigh the complexity. – Steffen Schumacher Sep 29 '20 at 08:07

2 Answers2

3

Your solution will work in your specific Enum, but as I pointed in the comment: it will also change the behavior of all enums in the same application - including private enums used deep inside any library (and even enum's in the stdlib).

The "clean" way of doing this, following Object Oriented inheritance rules is to: create a new metaclass, inheriting from the existing metaclass for Enum, and update the __contains__ method in this class.

Then create a new Enum base, using this new metaclass, but identical to Enum in other respects (achieved by simply writing it with an empty body):

import enum

class CustomEnumMeta(enum.EnumMeta):
    def __contains__(cls, element):
        # Mocking "contains"
        # It is usual to place "cls" instead of "self" in the metaclass
        # as the received "instance" will be a class in itself. 
        return True
    
class CustomEnum(Enum, metaclass=CustomEnumMeta):
    pass

class MyEnum(CustomEnum):
    a = 1
    b = 2
    

And in the interactive prompt:

In [10]: "x" in MyEnum                                                                       
Out[10]: True

In [11]: MyEnum.a                                                                            
Out[11]: <MyEnum.a: 1>

If you want to defer the to the normal containment rules after some custom logic, in the derived metaclass you can just call super().__contains__ after you are done with your personalized rules, like in:

...

def __contains__(cls, element):
    if member in cls._member_map_:
        return True
    return super().__contains__(cls, element)

It is just a bit different from what you did, but it does not duplicate the logic of the original __contains__ in Enum in your custom method.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
0

So I found out that one way is like so:

def __mycontains__(cls, member):
    # this part is stolen from EnumMetas __contains__
    if isinstance(member, cls):
        return member._name_ in cls._member_map_
    if not isinstance(member, str):
        raise ValueError('Illegal enum name: {}'.format(member))
    return member in cls._member_map_

class a(Enum):
   flaf = auto()

setattr(a.__class__, __contains__, __mycontains__)

This changes the function called when doing if 'flaf' in a: as needed. Possibly there are cases where this approach isn't best, and if so, I'd love to hear about it - for my part, I use this to match parsed text codes to enums, to bridge things to typed values rather than strings...

  • Yes - but this approach will set a new `__contains__` behavior for _all_ `__contains__` in all Enum's in your app - even Enums that exist internally in libraries that you don't know about, and which may ne used in a **in** operation. – jsbueno Sep 30 '20 at 21:47
  • The answer in the quesiton currently marked as duplicate of this is not appropriate eiher, as it does not take in account the existing Enum metaclass. – jsbueno Sep 30 '20 at 21:48