当我使用Functional API创建枚举时,我得到一个允许任意赋值的枚举对象(即它具有__dict __):
e = enum.Enum('Things',[('foo',1),('bar',2)])
e.baz = 3
该项目未出现在列表中:
list(e)
[<foo.foo: 1>, <foo.bar: 2>]
但仍可以引用:
if thing == e.baz: ...
现在,尽管似乎不太可能发生,但我想使用枚举的原因之一是为了防止拼写错误和字符串文字,以及在导入模块时或尽可能早地捕获这些错误。 / p>
是否有一种方法可以动态地构建一个行为更像__slots__对象的枚举,该对象不允许分配任意属性?
答案 0 :(得分:3)
不一定容易,但可能。我们需要创建一个新的EnumMeta
类型 1 ,正常创建Enum
,然后在创建Enum
之后重新分配类型:
from enum import Enum, EnumMeta
class FrozenEnum(EnumMeta):
"prevent creation of new attributes"
def __getattr__(self, name):
if name not in self._member_map_:
raise AttributeError('%s %r has no attribute %r'
% (self.__class__.__name__, self.__name__, name))
return super().__getattr__(name)
def __setattr__(self, name, value):
if name in self.__dict__ or name in self._member_map_:
return super().__setattr__(name, value)
raise AttributeError('%s %r has no attribute %r'
% (self.__class__.__name__, self.__name__, name))
class Color(Enum):
red = 1
green = 2
blue = 3
Color.__class__ = FrozenEnum
并在使用中:
>>> type(Color)
<class 'FrozenEnum'>
>>> list(Color)
[<Color.red: 1>, <Color.green: 2>, <Color.blue: 3>]
>>> Color.blue
<Color.blue: 3>
>>> Color.baz = 3
Traceback (most recent call last):
...
AttributeError: FrozenEnum 'Color' has no attribute 'baz'
>>> Color.baz
Traceback (most recent call last):
...
AttributeError: 'FrozenEnum' object has no attribute 'baz'
尝试重新分配成员仍然会出现更友好的错误:
>>> Color.blue = 9
Traceback (most recent call last):
...
AttributeError: Cannot reassign members.
为了使类的重新分配更加容易,我们可以编写一个装饰器来封装该过程:
def freeze(enum_class):
enum_class.__class__ = FrozenEnum
return enum_class
并在使用中:
@freeze
class Color(Enum):
red = 1
green = 2
blue = 3
请注意,仍然可以覆盖普通属性,例如函数:
@freeze
class Color(Enum):
red = 1
green = 2
blue = 3
def huh(self):
print("Huh, I am %s!" % self.name)
并在使用中:
>>> Color.huh
<function Color.huh at 0x7f7d54ae96a8>
>>> Color.blue.huh()
Huh, I am blue!
>>> Color.huh = 3
>>> Color.huh
3
>>> Color.blue.huh()
Traceback (most recent call last):
...
TypeError: 'int' object is not callable
即使可以阻止,但(暂时)我还是把它留给别人做。
1 这只是我所看到的第二种需要子类EnumMeta
的情况。有关其他内容,请参见this question
。
披露:我是Python stdlib Enum
,enum34
backport和Advanced Enumeration (aenum
)库的作者。
答案 1 :(得分:2)
要使枚举类完全“只读”,只需使用一个使用__setattr__
hook的元类,该元类将阻止 all 属性的分配。因为元类是在创建后附加到类 的,所以分配适当的枚举值没有问题。
就像Ethan的回答一样,我使用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
以上内容区分了现有属性和新属性,以便于诊断。它还会阻止属性删除,这可能同样重要!
它还提供元类和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.
请注意,不允许所有属性更改,不允许新属性,并且删除操作也被阻止。当名称是枚举成员时,我们将委托原始的EnumMeta
处理来保持错误消息的稳定。
如果您的枚举使用的属性会更改枚举类的属性,则您必须将其列入白名单,或者允许设置以单个下划线开头的名称;在__setattr__
中,确定可以设置哪些名称并将super().__setattr__(name, value)
用于这些异常,就像代码现在通过使用flag属性区分类构造和以后的更改一样。
以上类可以像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.