как переопределить метод метакласса python?

#python #inheritance #overriding #metaclass

#python #наследование #переопределение #метакласс

Вопрос:

Я почти убежден, что это невозможно, но вот что: у меня есть класс a(enum.Enum), где я хотел бы переопределить тот __contains__ , который унаследован от metaclass=enum .EnumMeta Возможно ли переопределить EnumMeta::__contains__ из моего класса a?

Я думаю, мне нужно дублировать все, чтобы достичь этого?

Комментарии:

1. В некотором смысле, а затем снова.. Я бы предпочел не заменять перечисление базового класса, как предлагается, что требует от меня дублирования всего содержимого класса Enum (нежелательно). Я нашел другой способ, который позволяет мне сохранить стандартные классы без изменений, так что, если они будут изменены в будущем, я все равно получу эти изменения, и, хотя это не классическое наследование, преимущества перевешивают сложность.

Ответ №1:

Ваше решение будет работать в вашем конкретном перечислении, но, как я указал в комментарии: оно также изменит поведение всех перечислений в одном приложении, включая частные перечисления, используемые глубоко внутри любой библиотеки (и даже перечисления в stdlib).

«Чистый» способ сделать это, следуя правилам объектно-ориентированного наследования, заключается в следующем: создайте новый метакласс, наследующий от существующего метакласса for Enum , и обновите __contains__ метод в этом классе.

Затем создайте новую Enum базу, используя этот новый метакласс, но идентичный Enum в других отношениях (достигается путем простой записи его с пустым телом):

 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
    
  

И в интерактивном приглашении:

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

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

Если вы хотите отложить до обычных правил сдерживания после некоторой пользовательской логики, в производном метаклассе вы можете просто вызвать super().__contains__ после того, как закончите с вашими персонализированными правилами, например, в:

 ...

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

Он немного отличается от того, что вы сделали, но он не дублирует логику оригинала __contains__ в Enum в вашем пользовательском методе.

Ответ №2:

Итак, я узнал, что один из способов выглядит так:

 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__)
  

Это изменяет вызываемую функцию при выполнении if 'flaf' in a: по мере необходимости.
Возможно, есть случаи, когда этот подход не самый лучший, и если да, я хотел бы услышать об этом — со своей стороны, я использую это для сопоставления проанализированных текстовых кодов с перечислениями, чтобы связать вещи с типизированными значениями, а не со строками…

Комментарии:

1.Да, но этот подход задаст новое __contains__ поведение для всех __contains__ всех перечислений в вашем приложении — даже перечислений, которые существуют внутри библиотек, о которых вы не знаете, и которые могут использоваться в процессе работы.

2. Ответ в вопросе, который в настоящее время помечен как дубликат этого, также не подходит, поскольку он не учитывает существующий метакласс Enum .