Me gustaría tener un indicador "TODOS" en mi enumeración de banderas de python para el cual
myenum.EVERY_MEMBER & myenum.ALL == myenum.EVERY_MEMBERse mantiene cierto. actualmente tengo:
from enum import Flag, auto class RefreshFlags(Flag): NONE = 0 EVENTS = auto() RESOURCES = auto() BUILDINGS = auto() DEFENSES = auto() .....Debido a que esta enumeración podría crecer en cualquier estado de desarrollo, me gustaría tener algo como
@property def ALL(self): retval = self.NONE for member in self.__members__.values(): retval |= member return retvalEsto no funciona:
RefreshFlags.EVENTS & RefreshFlags.ALL TypeError: unsupported operand type(s) for &: 'RefreshFlags' and 'property'Tenga en cuenta que esta pregunta actualmente solo se relaciona con Python 3.6 o posterior.
Hay algunas maneras de superar este problema:
use una propiedad de classproperty (vea Zero's answer )
use un decorador de clase (vea MSeifert's answer )
use un mixin ( currently buggy )
crear una nueva clase base (ver más abajo)
Una cosa a tener en cuenta con el método de propiedad de clase es que, dado que el descriptor se define en la clase y no en la metaclase, las protecciones habituales contra la configuración y la eliminación están ausentes, en otras palabras:
>>> RefreshFlags.ALL <RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15> >>> RefreshFlags.ALL = 'oops' >>> RefreshFlags.ALL 'oops'Creando una nueva clase base:
# 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 valueY en uso:
class RefreshFlag(AllFlag): EVENTS = auto() RESOURCES = auto() BUILDINGS = auto() DEFENSES = auto() >>> RefreshFlag.ALL <RefreshFlag.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15> La diferencia interesante en la propiedad ALL es la configuración del nombre en _member_map_ -- esto permite las mismas protecciones otorgadas a los miembros de Enum:
>>> RefreshFlag.ALL = 9 Traceback (most recent call last): .... AttributeError: Cannot reassign members. Sin embargo, hay una condición de carrera aquí: si RefreshFlag.ALL = ... ocurre antes de que RefreshFlag.ALL se active la primera vez, entonces se golpea; por esta razón, usaría un decorador en este caso, ya que el decorador procesará el Enum antes de que pueda ser golpeado.
# 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 enumerationY en uso:
@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>TL; DR Porque la propiedad solo se evalúa en instancias de una clase, mientras que los __members__ solo son accesibles en la clase.
Si accede a una property en una clase, simplemente devuelve una property :
>>> RefreshFlags.ALL <property at 0x2a5d93382c8>Sin embargo, para que esto funcione, debe convertirlo en un método de clase:
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>o acceda a la propiedad en una instancia:
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>En ambos casos puedes hacer tu comparación más adelante:
>>> RefreshFlags.EVENTS & RefreshFlags.EVENTS.ALL <RefreshFlags.EVENTS: 1> Usted declaró en los comentarios que desea que el miembro ALL se comporte como los demás, en ese caso, sugiero usar un decorador de clase:
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>El decorador de clase también se puede usar en otras enumeraciones :)
Siguiendo con la respuesta de MSeifert, es posible escribir un decorador @classproperty que le permita acceder a RefreshFlags.ALL directamente como una propiedad (en lugar de un método convencional o una propiedad en una instancia):
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) Por supuesto, puede escribir ALL() con un bucle for explícito como en su ejemplo; lo anterior se ofrece meramente como una alternativa.
>>> RefreshFlags.ALL <RefreshFlags.DEFENSES|BUILDINGS|RESOURCES|EVENTS: 15> >>> RefreshFlags.ALL & RefreshFlags.BUILDINGS <RefreshFlags.BUILDINGS: 4>