Подтвердить что ты не робот

Представление всех значений в перечислении Flag

Я хотел бы иметь флаг "ALL" в моем перечислении флагов python, для которого

myenum.EVERY_MEMBER & myenum.ALL == myenum.EVERY_MEMBER

выполняется. В настоящее время у меня есть:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()
    .....

Поскольку это перечисление может расти в любом состоянии развития, я хотел бы иметь что-то вроде

@property
def ALL(self):
    retval = self.NONE
    for member in self.__members__.values():
        retval |= member
    return retval

Это не работает:

RefreshFlags.EVENTS  & RefreshFlags.ALL

TypeError: unsupported operand type(s) for &: 'RefreshFlags' and 'property'

Обратите внимание, что этот вопрос в настоящее время относится только к python 3.6 или новее.

4b9b3361

Ответ 1

Есть несколько способов преодолеть эту проблему:

  • используйте classproperty (см. Zero answer)

  • используйте декоратор класса (см. MSeifert answer)

  • используйте mixin (currently buggy)

  • создать новый базовый класс (см. ниже)


Одна вещь, которую следует знать с помощью метода свойств класса, заключается в том, что дескриптор определен в классе, а не в метаклассе, обычная защита от установки и удаления отсутствует - другими словами:

>>> RefreshFlags.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

>>> RefreshFlags.ALL = 'oops'
>>> RefreshFlags.ALL
'oops'

Создание нового базового класса:

# lightly tested
from enum import Flag, auto
from operator import or_ as _or_
from functools import reduce

class AllFlag(Flag):

    @classproperty
    def ALL(cls):
        cls_name = cls.__name__
        if not len(cls):
            raise AttributeError('empty %s does not have an ALL value' % cls_name)
        value = cls(reduce(_or_, cls))
        cls._member_map_['ALL'] = value
        return value

И при использовании:

class RefreshFlag(AllFlag):
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlag.ALL
<RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

Интересной разницей в свойстве ALL является установка имени в _member_map_ - это позволяет те же самые защиты, что и члены Enum:

>>> RefreshFlag.ALL = 9
Traceback (most recent call last):
  ....
AttributeError: Cannot reassign members.

Однако здесь есть условие гонки: если RefreshFlag.ALL = ... происходит до активации RefreshFlag.ALL в первый раз, тогда он сбивается; по этой причине я бы использовал декоратор в этом случае, так как декоратор будет обрабатывать Enum до того, как он будет сплющен.

# lightly tested

from enum import Flag, auto
from operator import or_ as _or_
from functools import reduce

def with_limits(enumeration):
    "add NONE and ALL psuedo-members to enumeration"
    none_mbr = enumeration(0)
    all_mbr = enumeration(reduce(_or_, enumeration))
    enumeration._member_map_['NONE'] = none_mbr
    enumeration._member_map_['ALL'] = all_mbr
    return enumeration

И при использовании:

@with_limits
class RefreshFlag(Flag):
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlag.ALL = 99
Traceback (most recent call last):
  ...
AttributeError: Cannot reassign members.

>>> RefreshFlag.ALL 
<RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

>>> RefreshFlag.NONE
<RefreshFlag.0: 0>

Ответ 2

TL; DR Поскольку свойство оценивается только для экземпляров класса, а __members__ доступно только для класса.


Если вы получаете доступ к property в классе, он просто возвращает property:

>>> RefreshFlags.ALL
<property at 0x2a5d93382c8>

Чтобы сделать эту работу, вам нужно либо сделать ее классом:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @classmethod
    def ALL(cls):
        retval = self.NONE
        for member in cls.__members__.values():
            retval |= member
        return retval

>>> RefreshFlags.ALL()
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

или получить доступ к свойству экземпляра:

from enum import Flag, auto

class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @property
    def ALL(self):
        retval = self.NONE
        # One needs to access .__class__ here!
        for member in self.__class__.__members__.values():
            retval |= member
        return retval

>>> RefreshFlags.EVENTS.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>

В обоих случаях вы можете выполнить свое сравнение позже:

>>> RefreshFlags.EVENTS & RefreshFlags.EVENTS.ALL
<RefreshFlags.EVENTS: 1>

Вы указали в комментариях, что хотите, чтобы член ALL вел себя как другие, в этом случае я предлагаю использовать декоратор класса:

def with_ALL_member(enumeration):
    retval = enumeration(0)  # in case NONE is not defined
    for name, member in enumeration.__members__.items():
        retval |= member
    enumeration.ALL = retval
    return enumeration

@with_ALL_member
class RefreshFlags(Flag):
    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

>>> RefreshFlags.EVENTS & RefreshFlags.ALL
<RefreshFlags.EVENTS: 1>

>>> RefreshFlags.DEFENSES & RefreshFlags.ALL
<RefreshFlags.DEFENSES: 8>

Декоратор класса также может быть использован для других перечислений:)

Ответ 3

В ответ на MSeifert answer можно написать декоратор @classproperty, который позволяет вам напрямую обращаться к RefreshFlags.ALL как к свойству (а не как к обычным метод или свойство в экземпляре):

from enum import Flag, auto
from operator import or_
from functools import reduce


class classproperty:

    def __init__(self, func):
        self._func = func

    def __get__(self, obj, owner):
        return self._func(owner)


class RefreshFlags(Flag):

    NONE = 0
    EVENTS = auto()
    RESOURCES = auto()
    BUILDINGS = auto()
    DEFENSES = auto()

    @classproperty
    def ALL(cls):
        return reduce(or_, cls)

Конечно, вы можете написать ALL() с явным циклом for, как в вашем примере; вышеуказанное просто предлагается в качестве альтернативы.

>>> RefreshFlags.ALL
<RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15>
>>> RefreshFlags.ALL & RefreshFlags.BUILDINGS
<RefreshFlags.BUILDINGS: 4>