是否有用于标志/位掩码操作的Python类/枚举?

时间:2016-04-24 22:53:21

标签: python-3.x enums bitwise-operators

我知道基类EnumIntEnum。两者都非常有用,但我错过了旗帜操作的功能。我不希望这两个类实现我希望的功能。

让我们构建一个例子:

class NetlistKind(IntEnum):
  Unknown = 0
  LatticeNetlist = 1
  QuartusNetlist = 2
  XSTNetlist = 4
  CoreGenNetlist = 8
  All = 15

如您所见,我已经使用IntEnum来获取此枚举的算术功能。像@unique这样的东西可以确保所有值都是2的幂。我可以通过为我的需求分配enum.unique来做到这一点。 (我知道All是该规则的例外情况。)

如何使用这样的枚举?

filter = NetlistKind.LatticeNetlist | NetlistKind.QuartusNetlist

由于底层的int位操作是可能的,并且filter的内部值为3。

如果在过滤器Y"中设置"标志X会很好。功能甚至更好的操作员。我为x in y添加了一个魔术函数:

@unique
class NetlistKind(IntEnum):
  Unknown = 0
  LatticeNetlist = 1
  QuartusNetlist = 2
  XSTNetlist = 4
  CoreGenNetlist = 8
  All = 15

def __contains__(self, item):
  return  (self.value & item.value) == item.value

用法示例:

....
def GetNetlists(self, filter=NetlistKind.All):
  for entity in self._entities:
    for nl in entity.GetNetlists():
      if (nl.kind in filter):
        yield nl

def GetXilinxNetlists(self):
  return self.GetNetlists(NetlistKind.XSTNetlist | NetlistKind.CoreGenNetlist)

所以问题是:

  • 是否有更好的方法来实现位字段?
  • 是否有更好的方法来实现这样的1-D滤波器?我不想在这么简单的过滤条件下使用lamdas吗?
  • 这样的解决方案是否已包含在Python标准库中?
  • 如何将此枚举扩展添加到下一个Python版本? :)

打开功能:

  • 返回__str__
  • 中所有有效标记的列表
  • ...?

2 个答案:

答案 0 :(得分:22)

Python 3.6添加了FlagIntFlag,它们支持通常的按位操作。作为奖励,来自逐位操作的结果值仍然是原始标志类的成员,并且是单例[1]。

aenum库也有这个添加,可以用回Python 2.7。

[1] 3.6.0中存在一个错误:如果在线程中创建了psuedo-flag成员,那么最终可能会出现重复;这已在3.6.1中修复(并且从未存在于aenum中)。

答案 1 :(得分:8)

我最近发布了一个解决此问题的开源软件包py-flags。该库具有这种功能,其设计受到python3枚举模块的严重影响。

关于是否pythonic足以实现这样的标志类存在争论,因为它的功能与语言提供的其他方法(bool变量集合,集合,具有bool属性的对象或具有bool项目的dicts的集合)存在巨大重叠。 ..)。出于这个原因,我觉得标志类太过狭窄的目的和/或冗余,无法进入标准库,但在某些情况下,它比以前列出的解决方案要好得多,所以有一个" pip install" - 图书馆可以派上用场。

使用py-flags模块,您的示例如下所示:

from flags import Flags

class NetlistKind(Flags):
    Unknown = 0
    LatticeNetlist = 1
    QuartusNetlist = 2
    XSTNetlist = 4
    CoreGenNetlist = 8
    All = 15

上面的内容可以进一步调整,因为用库声明的标志类会自动提供两个"虚拟"标志:NetlistKind.no_flagsNetlistKind.all_flags。这些使已经声明的NetlistKind.UnknownNetlistKind.All变得多余,因此我们可以将它们从声明中排除,但问题是no_flagsall_flags与您的命名不匹配惯例。为了解决这个问题,我们在项目中声明了一个标志基类,而不是flags.Flags,你必须在项目的其余部分使用它:

from flags import Flags

class BaseFlags(Flags):
    __no_flags_name__ = 'Unknown'
    __all_flags_name__ = 'All'

基于先前声明的基类,可以通过项目中的任何标志进行子类化,我们可以将您的标志声明更改为:

class NetlistKind(BaseFlags):
    LatticeNetlist = 1
    QuartusNetlist = 2
    XSTNetlist = 4
    CoreGenNetlist = 8

这种方式NetlistKind.Unknown自动声明为零值。 NetlistKind.All也在那里,它自动是所有声明的标志的组合。可以使用/不使用这些虚拟标志来迭代枚举成员。您还可以声明别名(与另一个先前声明的标志具有相同值的标志)。

作为使用"函数调用样式的替代声明" (也由标准枚举模块提供):

NetlistKind = BaseFlags('NetlistKind', ['LatticeNetlist', 'QuartusNetlist',
                                        'XSTNetlist', 'CoreGenNetlist'])

如果flags类声明某些成员,那么它被认为是final。试图将其子类化将导致错误。在语义上不希望允许为了添加新成员或更改功能而对子类进行子类化。

除此之外,flags类以类型安全的方式为您列出的运算符(bool运算符,in,iteration等...)提供。我将在接下来的几天内完成README.rst以及包界面上的一些管道,但基本功能已经存在,并且测试覆盖率非常好。