Python枚举防止无效的属性分配 [英] Python enum prevent invalid attribute assignment

查看:113
本文介绍了Python枚举防止无效的属性分配的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当我使用Functional API创建枚举时,我得到一个枚举对象,该对象允许任意赋值(即它具有__dict __):

When I use the Functional API to create an enum, I get back an enum object that allows arbitrary assignment (i.e. it has a __dict__):

e = enum.Enum('Things',[('foo',1),('bar',2)])
e.baz = 3

该项目未出现在列表中:

The item does not appear in the list:

list(e)
[<foo.foo: 1>, <foo.bar: 2>]

但仍可以引用:

if thing == e.baz: ...

现在虽然似乎不太可能发生,但我想使用枚举是为了防止拼写错误和字符串文字,以及防止在导入模块时或尽可能早地捕获这些错误。

Now while it seems unlikely to ever occur, one of the reasons I want to use an enum is to prevent spelling mistakes and string literals, and for those things to be caught when a module is imported or as early as possible.

有没有一种方法可以动态构建一个枚举的行为更像一个__slots__对象,不允许分配任意属性?

Is there a way to dynamically build an enum that behaves more like a __slots__ object that does not allow arbitrary attributes to be assigned?

推荐答案

要完全使枚举类全部为只读所需的是使用 的元类。 __setattr __ 钩子,用于阻止 all 属性分配。因为元类在创建后附加到类 ,所以分配适当的枚举值没有问题。

To make an enum class fully 'read-only', all that is required is a meta class that uses the __setattr__ hook that prevents all attribute assignments. Because the metaclass is attached to the class after it is created, there is no issue with assigning the proper enumerated values.

就像Ethan的回答,我使用 EnumMeta 类作为自定义元类的基础:

Like Ethan's answer, I'm using the EnumMeta class as a base for the custom metaclass:

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

以上内容区分了已有的属性和新属性,以便于诊断。它还会阻止属性删除,这可能同样重要!

The above distinguishes between attributes that are already available and new attributes, for ease of diagnosing. It also blocks attribute deletion, which is probably just as important!

它还提供元类和 FrozenEnum 基类进行枚举;使用它代替 Enum

It also provides both the metaclass and a FrozenEnum base class for enumerations; use this instead of Enum.

冻结样本颜色枚举:

>>> 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 处理以保持错误消息的稳定。

Note that all attribute changes are disallowed, no new attributes permitted, and deletions are blocked too. When names are enum members, we delegate to the original EnumMeta handling to keep the error messages stable.

如果您的枚举使用更改枚举类上属性的属性,您要么必须将其列入白名单,要么允许设置以单个下划线开头的名称;在 __ setattr __ 中确定允许设置哪些名称,并使用 super()。__setattr __(name,value)例外,就像现在的代码通过使用flag属性来区分类构造和以后的更改一样。

If your enum uses properties that alter attributes on the enum class, you'd either have to whitelist those, or allow for names starting with a single underscore to be set; in __setattr__ determine what names would be permissible to set and use super().__setattr__(name, value) for those exceptions, just like the code now distinguishes between class construction and later alterations by using a flag attribute.

上面的类可以像 Enum一样使用()以编程方式创建枚举:

The above class can be used just like Enum() to programmatically create an enumeration:

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.

这篇关于Python枚举防止无效的属性分配的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆