web-dev-qa-db-fra.com

Existe-t-il une classe Python / énum pour les opérations d'indicateur / masque de bits?

Je connais les classes de base Enum et IntEnum. Les deux sont très utiles mais je manque des fonctionnalités pour les opérations de drapeau. Je ne m'attends pas à ce que ces deux classes implémentent ma fonctionnalité souhaitée.

Construisons un exemple:

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

Comme vous pouvez le voir, j'utilise déjà IntEnum pour obtenir des fonctionnalités arithmétiques pour cette énumération. Ce serait bien d'avoir quelque chose comme @unique pour garantir que toutes les valeurs sont une puissance de deux. Je peux le faire en forçant enum.unique à mes besoins. (Je sais que All est une exception à cette règle.)

Comment une telle énumération est-elle utilisée?

filter = NetlistKind.LatticeNetlist | NetlistKind.QuartusNetlist

Grâce au sous-jacent, les opérations int bit sont possibles et le filtre a une valeur interne de 3.

Si ce serait bien d'avoir une fonction "is flag X set in filter Y" ou encore mieux un opérateur. J'ajoute une fonction magique pour 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

Exemple d'utilisation:

....
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)

Les questions sont donc:

  • Existe-t-il de meilleures façons de mettre en œuvre des champs de bits?
  • Ces moyens sont-ils meilleurs pour implémenter un tel filtre 1-D? Je ne veux pas utiliser de lamdas pour une condition de filtre aussi simple?
  • Une telle solution est-elle déjà incluse dans la bibliothèque standard Python?
  • Comment ajouter cette extension d'énumération à la prochaine Python? :)

Fonctionnalités ouvertes:

  • retourne une liste de tous les drapeaux actifs dans __str__
  • ...?
18
Paebbels

J'ai récemment publié un paquet open source py-flags qui vise ce problème. Cette bibliothèque possède exactement cette fonctionnalité et sa conception est fortement influencée par le module d'énumération python3.

Il y a des débats pour savoir s'il est suffisamment pythonique pour implémenter une telle classe de drapeaux parce que sa fonctionnalité a d'énormes chevauchements avec d'autres méthodes fournies par le langage (collection de variables booléennes, ensembles, objets avec attributs booléens ou dict avec éléments booléens, ...) . Pour cette raison, je pense qu'une classe de drapeaux est trop étroite et/ou redondante pour se rendre à la bibliothèque standard, mais dans certains cas, elle est bien meilleure que les solutions répertoriées précédemment, donc avoir une bibliothèque compatible "pip install" peut venir en pratique.

Votre exemple ressemblerait à ceci en utilisant le module py-flags:

from flags import Flags

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

Les choses ci-dessus pourraient être modifiées un peu plus car une classe de drapeaux déclarée avec la bibliothèque fournit automatiquement deux drapeaux "virtuels": NetlistKind.no_flags Et NetlistKind.all_flags. Ceux-ci rendent les NetlistKind.Unknown Et NetlistKind.All Déjà déclarés redondants afin que nous puissions les exclure de la déclaration, mais le problème est que no_flags Et all_flags Ne correspondent pas votre convention de dénomination. Pour faciliter cela, nous déclarons une classe de base de drapeaux dans votre projet au lieu de flags.Flags Et vous devrez l'utiliser dans le reste de votre projet:

from flags import Flags

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

Sur la base de la classe de base précédemment déclarée qui peut être sous-classée par l'un de vos indicateurs dans votre projet, nous pourrions changer votre déclaration d'indicateur en:

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

De cette façon, NetlistKind.Unknown Est automatiquement déclaré avec une valeur de zéro. NetlistKind.All Est également là et c'est automatiquement la combinaison de tous vos drapeaux déclarés. Il est possible d'itérer les membres enum avec/sans ces indicateurs virtuels. Vous pouvez également déclarer des alias (indicateurs qui ont la même valeur qu'un autre indicateur précédemment déclaré).

Comme déclaration alternative en utilisant le "style d'appel de fonction" (également fourni par le module d'énumération standard):

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

Si une classe flags déclare certains membres, elle est considérée comme finale. Toute tentative de sous-classe entraînera une erreur. Il n'est pas sémantiquement souhaitable d'autoriser le sous-classement d'une classe d'indicateur dans le but d'ajouter de nouveaux membres ou de modifier la fonctionnalité.

En plus de cela, la classe flags fournit les opérateurs listés (opérateurs booléens, dans, itération, etc ...) de manière sécurisée. Je vais terminer le fichier README.rst avec un peu de plomberie sur l'interface du package dans les prochains jours, mais la fonctionnalité de base est déjà là et testée avec une assez bonne couverture.

14
pasztorpisti

Python 3.6 a ajouté Flag et IntFlag qui prennent en charge les opérations bit à bit habituelles. En prime, les valeurs résultantes des opérations bit par bit sont toujours membres de la classe de drapeau d'origine et sont des singletons [1].

La bibliothèque aenum a également cet ajout et est réutilisable à Python 2.7.

[1] Un bogue existe dans la version 3.6.0: si les membres du pseudo-drapeau sont créés dans des threads, il peut y avoir des doublons; cela est corrigé dans 3.6.1 (et n'a jamais existé dans aenum).

31
Ethan Furman