Существует ли класс/перечисление Python для операций с флагом/битовой маской?

Я знаю базовые классы Enum и IntEnum. Оба очень полезны, но я пропускаю функции для операций с флагом. Я не ожидаю, что эти два класса реализуют мою желаемую функцию.

Возьмем пример:

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

Как вы можете видеть, я уже использую IntEnum для получения арифметических функций для этого перечисления. Было бы неплохо иметь что-то вроде @unique чтобы обеспечить, чтобы все ценности были силой двух. Я могу сделать это, нарисуя enum.unique для моих нужд. (Я знаю, что All является исключением из этого правила.)

Как такое перечисление используется?

filter = NetlistKind.LatticeNetlist | NetlistKind.QuartusNetlist

Благодаря подстилающим операциям int бит возможны и фильтр имеет внутреннее значение 3.

Если было бы неплохо иметь функцию "flag X set in filter Y" или даже лучше оператора. Я добавляю магическую функцию для 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__
  • ...?

Ответы

Ответ 1

Я недавно опубликовал пакет с открытым исходным кодом PY-флаги, что цели этой проблемы. Эта библиотека имеет именно эту функциональность, и ее дизайн сильно зависит от модуля переименования python3.

Есть дебаты о том, достаточно ли для pythonic реализовать такой класс флагов, поскольку его функциональность имеет огромные совпадения с другими методами, предоставляемыми языком (коллекция переменных bool, наборов, объектов с атрибутами bool или dicts с элементами bool,...), По этой причине я считаю, что класс флагов слишком узкий и/или избыточный, чтобы пробиваться к стандартной библиотеке, но в некоторых случаях он намного лучше, чем ранее перечисленные решения, поэтому наличие библиотеки "pip install" -able может пригодиться.

Ваш пример будет выглядеть следующим образом, используя модуль 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 также существует, и он автоматически представляет собой комбинацию всех ваших объявленных флагов. Можно итерации перечислять члены с/без этих виртуальных флагов. Вы также можете объявлять псевдонимы (флаги, которые имеют то же значение, что и ранее объявленный флаг).

В качестве альтернативного объявления, использующего "стиль функционального вызова" (также предоставляемый стандартным модулем enum):

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

Если класс flags объявляет некоторые члены, то он считается окончательным. Попытка подкласса приведет к ошибке. Семантически нежелательно разрешать подкласс класса флага с целью добавления новых членов или изменения функциональных возможностей.

Помимо этого, класс flags предоставляет операторы, перечисленные вами (операторы bool, in, iteration и т.д.) Безопасным образом. Я собираюсь закончить README.rst вместе с небольшой настройкой интерфейса пакета в ближайшие несколько дней, но базовые функции уже есть и протестированы с неплохим охватом.

Ответ 2

Python 3.6 добавил Flag и IntFlag которые поддерживают обычные IntFlag операции. В качестве бонуса результирующие значения из битовых операций по-прежнему являются членами исходного класса флагов и являются одноточечными [1].

Библиотека aenum также имеет это дополнение и может использоваться для Python 2.7.

[1] Ошибка в 3.6.0: если члены флага psudeo создаются в потоках, тогда в итоге могут быть дубликаты; это зафиксировано в 3.6.1 (и никогда не существовало в aenum).