【问题标题】:Is there a Python class/enum for flag/bit mask operations?是否有用于标志/位掩码操作的 Python 类/枚举?
【发布时间】:2016-04-24 22:53:21
【问题描述】:

我知道基类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 位操作是可能的,并且过滤器的内部值为 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__ 中所有活动标志的列表
  • ...?

【问题讨论】:

  • 我最近为我的标志库配备了单元测试并将其发布在 pypi 上。我将在接下来的几天内完成它的 README.rst 并添加一些额外的功能。它的接口深受python3标准枚举模块的影响。如果您有兴趣,请查看:pypi.python.org/pypi/py-flags 我已经看到有关标志是否是 Python 方法的讨论。我未来对 README.rst 的更新将有一个部分讨论使用多个布尔值作为函数参数或将布尔值存储在对象或 dict VS 使用集合 VS 使用标志的优缺点。
  • 请发表您的评论作为答案,这样我就可以投票了!它看起来非常好和成熟。只有一个问题:为什么我需要给枚举一个 FQN?示例:TextStyle('TextStyle.bold')。我认为bold 就足够了,因为命名空间已经被限制为TextStyle,因为你将它传递给它的构造函数。
  • 不欢迎仅链接答案,所以恐怕......枚举的str()不仅可以在序列化的情况下用于其他上下文,这就是__str__的原因返回 fqdn。我认为 str() 即使没有上下文中的标志类也应该是可解释的。实际上,出于自定义序列化的目的,除了标准的__str__ 之外,我还提供了一个to_simple_str()。在这种情况下,to_simple_str() 将简单地发出 'bold' 并且 TextStyle('bold') 也可以工作。实际上flags的pickle序列化器支持只保存flags类名和to_simple_str()的输出。
  • 我实际上在考虑是否在str() 中返回 fqdn。我的想法是从 str() 返回一个非 fqdn,并仅将 fqdn 作为 repr() 的一部分返回,也许还有一个实用函数,但最后我决定使用 fqdn 以与 str() 的行为方式相同标准enum 模块。
  • 仅关于链接的答案:添加一个 py-flags 库的示例,它使用您的 classes/syntax/... 替换我的初始示例。然后添加指向 PyPI 的链接(文档,下载)。我对我的图书馆以同样的方式这样做,我从来没有遇到过关于仅链接帖子或垃圾邮件的问题。如果您解释您的解决方案如何解决我的问题然后链接到通用下载平台,则完全有效。如果它是开源的而不是商业产品,则规则会放宽:)

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


【解决方案1】:

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

aenum 库也有这个附加功能,并且可以在 Python 2.7 中使用。

[1] 3.6.0 中存在一个错误:如果伪标志成员是在线程中创建的,那么最终可能会出现重复;这在 3.6.1 中已修复(在 aenum 中从未存在过)。

【讨论】:

  • 谢谢伊桑。 @pasztorpisti 提供的 py-flags 模块非常强大。也许 Python 3.6 应该研究他的模块(基于元类)并结合一些特性。 => github.com/pasztorpisti/py-flags
  • @Paebbels:看起来它与 stdlib 版本非常相似。
  • @Paebbels 就基本功能而言,它们几乎是等价的,因此 python3.6+ 用户应该考虑 std lib 版本。 py-flags 包含一些额外的特性,这些特性可以被认为是好事或坏事,如果需要,开发人员可以使用继承将这些特性添加到标准库版本中。 py-flags 故意避免从 Enum 派生 Flags 作为设计决策。我认为枚举和标志之间的正确关系(如果需要)应该看起来像枚举和“枚举集”类型的 pascal 之间的关系:wiki.freepascal.org/Set
  • aenum 库很棒,当我发现只有 3.6 有新的 Flag 东西时,正是我所需要的。我不能完全确定它是否只是采用和反向移植的 3.6 代码或什么,但它运作良好。
【解决方案2】:

我最近发布了一个针对这个问题的开源包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_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 类以类型安全的方式提供您列出的运算符(布尔运算符、in、迭代等)。在接下来的几天里,我将完成 README.rst 并在包界面上进行一些尝试,但基本功能已经存在,并且经过了很好的测试。

【讨论】:

  • 啊,我应该在接受你的回答之前就开始赏金。现在链接消失了 :(。所以几乎 +100 的整体工作和用户定义的基类的提示覆盖 *_flags 名称。它就像一个魅力。
  • @Paebbels 感谢您的慷慨,但我通常不是为了积分。当我太累而无法做其他事情时,我会访问 stackoverflow。 :-D 我正在研究 lib,它是 README.rst 文件。一个更详细的文档,具有更好的界面描述和一些设计理念,将在接下来的几天内发布。如您所见,当前文档非常基础,但即使在较大文档的情况下,我通常也会从类似的 TL;DR 部分开始...如果您对即将推出的更长版本感兴趣,请轮询 lib 的 pypi 或 github 页面。
  • @Paebbels 对于应用程序,人们通常只使用requirements.txt,每行有1个依赖项和pip install -r requirements.txt。 requirements.txt 的每一行都包含一个 pip install 的参数列表,例如:whatever_libmock>=1.2.0-r inherited_base_requirements.txt
  • 我想我发现了一个小问题:None 在 Python 中是关键字,没有标识符,因此不能用作枚举成员。 NetlistKind.None 报告语法错误。我已经在一个很深的子目录中有 Travis-CI 的 requirements.lst ...我将把它移到根目录中。谢谢。
  • @Paebbels 将带有固定版本的依赖项添加到requirements.txt,例如:flags==1.0.1 而不仅仅是flags。这样,如果在 pypi 中更新了一个库(或者在最坏的情况下是多个库),那么您的应用程序将不会受到影响。当您明确需要时,最好手动升级和增加版本号。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-08-17
  • 1970-01-01
  • 1970-01-01
  • 2014-09-08
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
相关资源
最近更新 更多