Чтобы сделать перечисляющий класс полностью доступным только для чтения, все, что требуется, - это метакласс, который использует __setattr__
hook , который предотвращает все присвоения атрибутов.Поскольку метакласс присоединяется к классу после создания , нет проблем с назначением правильных перечисляемых значений.
Как и ответ Этана, я использую класс EnumMeta
какоснова для пользовательского метакласса:
from enum import EnumMeta, Enum
class FrozenEnumMeta(EnumMeta):
"Enum metaclass that freezes an enum entirely"
def __new__(mcls, name, bases, classdict):
classdict['__frozenenummeta_creating_class__'] = True
enum = super().__new__(mcls, name, bases, classdict)
del enum.__frozenenummeta_creating_class__
return enum
def __call__(cls, value, names=None, *, module=None, **kwargs):
if names is None: # simple value lookup
return cls.__new__(cls, value)
enum = Enum._create_(value, names, module=module, **kwargs)
enum.__class__ = type(cls)
return enum
def __setattr__(cls, name, value):
members = cls.__dict__.get('_member_map_', {})
if hasattr(cls, '__frozenenummeta_creating_class__') or name in members:
return super().__setattr__(name, value)
if hasattr(cls, name):
msg = "{!r} object attribute {!r} is read-only"
else:
msg = "{!r} object has no attribute {!r}"
raise AttributeError(msg.format(cls.__name__, name))
def __delattr__(cls, name):
members = cls.__dict__.get('_member_map_', {})
if hasattr(cls, '__frozenenummeta_creating_class__') or name in members:
return super().__delattr__(name)
if hasattr(cls, name):
msg = "{!r} object attribute {!r} is read-only"
else:
msg = "{!r} object has no attribute {!r}"
raise AttributeError(msg.format(cls.__name__, name))
class FrozenEnum(Enum, metaclass=FrozenEnumMeta):
pass
В приведенном выше описании различаются атрибуты, которые уже доступны, и новые атрибуты для упрощения диагностики.Он также блокирует атрибут delete , что, вероятно, так же важно!
Он также предоставляет метакласс и базовый класс FrozenEnum
для перечислений;используйте это вместо Enum
.
Чтобы заморозить образец Color
перечисление:
>>> class Color(FrozenEnum):
... red = 1
... green = 2
... blue = 3
...
>>> list(Color)
[<Color.red: 1>, <Color.green: 2>, <Color.blue: 3>]
>>> Color.foo = 'bar'
Traceback (most recent call last):
# ...
AttributeError: 'Color' object has no attribute 'foo'
>>> Color.red = 42
Traceback (most recent call last):
# ...
Cannot reassign members.
>>> del Color.red
Traceback (most recent call last):
# ...
AttributeError: Color: cannot delete Enum member.
Обратите внимание, что все изменения атрибута запрещены, новые атрибуты не разрешеныи удаления тоже заблокированы.Когда имена являются членами enum, мы делегируем исходную обработку EnumMeta
для поддержания стабильности сообщений об ошибках.
Если ваше enum использует свойства, которые изменяют атрибуты в классе enum, вы должны либо внести их в белый список,или разрешить указывать имена, начинающиеся с одного подчеркивания;в __setattr__
определите, какие имена было бы допустимо установить и использовать super().__setattr__(name, value)
для этих исключений, точно так же, как код теперь различает построение класса и последующие изменения с помощью атрибута флага.
Вышеприведенный класс может бытьиспользуется так же, как Enum()
, чтобы программно создать перечисление:
e = FrozenEnum('Things', [('foo',1), ('bar',2)]))
Демо:
>>> e = FrozenEnum('Things', [('foo',1), ('bar',2)])
>>> e
<enum 'Things'>
>>> e.foo = 'bar'
Traceback (most recent call last):
# ...
AttributeError: Cannot reassign members.