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

问题描述 投票:0回答:3

我知道基类

Enum
IntEnum
。两者都非常有帮助,但我怀念标志操作的功能。我不希望这两个类实现我想要的功能。

让我们构建一个示例:

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 位操作是可能的,并且过滤器的内部值为 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)

所以问题是:

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

开放功能:

  • 返回
    __str__
  • 中所有活动标志的列表
  • ...?
python-3.x enums bitwise-operators
3个回答
60
投票

Python 3.6 添加了

Flag
IntFlag
,支持通常的按位运算。作为奖励,按位运算的结果值仍然是原始标志类的成员,并且是单例 [1]。

aenum
库也添加了此功能,并且可以返回到 Python 2.7。

[1] 3.6.0 中存在一个错误:如果在线程中创建伪标志成员,则不能保证最终不会出现重复项;这在 3.6.1 中已修复(并且在

aenum
中从未存在过)。


24
投票

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

关于它是否足够Pythonic来实现这样的标志类存在争议,因为它的功能与该语言提供的其他方法有很大的重叠(布尔变量、集合、具有布尔属性的对象或具有布尔项的字典的集合,...... )。出于这个原因,我觉得标志类的目的太狭窄和/或多余,无法进入标准库,但在某些情况下,它比前面列出的解决方案要好得多,因此可以使用“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_flags
NetlistKind.all_flags
。这些使得已经声明的
NetlistKind.Unknown
NetlistKind.All
变得多余,因此我们可以将它们从声明中删除,但问题是
no_flags
all_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 类以类型安全的方式提供您列出的运算符(布尔运算符、in、迭代等...)。我将在接下来的几天内完成 README.rst 以及对包接口的一些了解,但基本功能已经存在,并且经过了相当好的覆盖范围的测试。


8
投票

我觉得这里有一个原生 Python 标志和按位运算的示例会很好。

from enum import Flag, auto


class FoodCategory(Flag):
    FISH = auto()
    SHELLFISH = auto()
    TREE_NUTS = auto()
    PEANUTS = auto()
    WHEAT = auto()
    RICE = auto()
    MILK = auto()
    BUTTER = auto()
    ALLERGENS = TREE_NUTS | PEANUTS | WHEAT | MILK


if __name__ == "__main__":
    DAIRY = FoodCategory.MILK | FoodCategory.BUTTER
    print(f"{FoodCategory.MILK in DAIRY = }")
    print(f"{FoodCategory.SHELLFISH in DAIRY = }")
    print(f"{FoodCategory.WHEAT in FoodCategory.ALLERGENS = }")
    print(f"{FoodCategory.SHELLFISH in FoodCategory.ALLERGENS = }")

输出:

FoodCategory.MILK in DAIRY = True
FoodCategory.SHELLFISH in DAIRY = False
FoodCategory.WHEAT in FoodCategory.ALLERGENS = True
FoodCategory.SHELLFISH in FoodCategory.ALLERGENS = False
© www.soinside.com 2019 - 2024. All rights reserved.