Skip to main content

Type-safe (bit)flags for python 3

Project description

build code quality code health coverage pypi github license: MIT

Quick Intro

With this module you can define type-safe (bit)flags. The style of the flag definition is very similar to the enum definitions you can create using the standard enum module of python 3.

Defining flags with the class syntax:

>>> from flags import Flags

>>> class TextStyle(Flags):
>>>     bold = 1            # value = 1 << 0
>>>     italic = 2          # value = 1 << 1
>>>     underline = 4       # value = 1 << 2

In most cases you just want to use the flags as a set (of bool variables) and the actual flag values aren’t important. To avoid manually setting unique flag values you can use auto assignment. To auto-assign a unique flag value use an empty iterable (for example empty tuple or list) as the value of the flag. Auto-assignment picks the first unused least significant bit for each auto-assignable flag in top-to-bottom order.

>>> class TextStyle(Flags):
>>>     bold = ()           # value = 1 << 0
>>>     italic = ()         # value = 1 << 1
>>>     underline = ()      # value = 1 << 2

As a shortcut you can call a flags class to create a subclass of it. This pattern has also been stolen from the standard enum module. The following flags definition is equivalent to the previous definition that uses the class syntax:

>>> TextStyle = Flags('TextStyle', 'bold italic underline')

Flags have human readable string representations and repr with more info:

>>> print(TextStyle.bold)
TextStyle.bold
>>> print(repr(TextStyle.bold))
<TextStyle.bold bits=0x0001 data=None>

The type of a flag is the flags class it belongs to:

>>> type(TextStyle.bold)
<class '__main__.TextStyle'>
>>> isinstance(TextStyle.bold, TextStyle)
True

You can combine flags with bool operators. The result is also an instance of the flags class with the previously described properties.

>>> result = TextStyle.bold | TextStyle.italic
>>>
>>> print(result)
TextStyle(bold|italic)
>>> print(repr(result))
<TextStyle(bold|italic) bits=0x0003>

Operators work in a type-safe way: you can combine only flags of the same type. Trying to combine them with instances of other types results in error:

>> result = TextStyle.bold | 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'TextStyle' and 'int'
>>>
>>> class OtherFlags(Flags):
...     flag0 = ()
...
>>> result = TextStyle.bold | OtherFlags.flag0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for |: 'TextStyle' and 'OtherFlags'

Flags and their combinations (basically the instances of the flags class) are immutable and hashable so they can be used as set members and dictionary keys:

>>> font_files = {}
>>> font_files[TextStyle.bold] = 'bold.ttf'
>>> font_files[TextStyle.italic] = 'italic.ttf'
>>> font_files == {TextStyle.bold: 'bold.ttf', TextStyle.italic: 'italic.ttf'}
True

The flags you define automatically have two “virtual” flags: no_flags and all_flags. no_flags is basically the zero flag and all_flags is the combination of all flags you’ve defined:

>>> TextStyle.no_flags
<TextStyle() bits=0x0000>
>>> TextStyle.all_flags
<TextStyle(bold|italic|underline) bits=0x0007>

Testing whether specific flags are set:

>>> result = TextStyle.bold | TextStyle.italic
>>> bool(result & TextStyle.bold)       # 1. oldschool bit twiddling
True
>>> TextStyle.bold in result            # 2. in operator
True
>>> result.bold                         # 3. attribute-style access
True

>From the above testing methods the attribute-style access can only check the presence of a single flag. With the & and in operators you can check the presence of multiple flags at the same time:

>>> bool((TextStyle.bold | TextStyle.italic) & TextStyle.all_flags)
True
>>> (TextStyle.bold | TextStyle.italic) in TextStyle.all_flags
True

If for some reason you need the actual integer value of the flags then you can cast them to int:

>>> int(TextStyle.bold)
1

You can convert the int() and str() representations of flags back into flags instances:

>>> TextStyle(2)
<TextStyle.italic bits=0x0002 data=None>
>>> TextStyle('TextStyle.bold')
<TextStyle.bold bits=0x0001 data=None>

Installation

pip install py-flags

Alternatively you can download the distribution from the following places:

Introduction

We can use integers as an effective storage for a bunch of bool variables packed together. If we name the individual bits of the integer and we aren’t interested in their individual position/value then we can also treat our integer as:

  • A set: a named bool variable - a bit from the integer - is part of the set if it’s value is True/1.

  • An object that has (of course named) bool attributes.

As usual this has its own pros and cons:

Pros:

  • You can store several bool values in a single object:

    • It’s easy to pass it as a single function parameter and function signatures don’t have to be refactored in case of adding/removing bool variables that are part of the flag.

  • Performing bool operations on flags can result in simpler and easier to read code than doing the same with individual bool variables that make up the integer.

Cons:

treat flags as a bunch of bool variables packed together into an integer. If we give each bit a name in the integer and the actual integer value of each bit doesn’t matter then we can also treat this as a set of items/flags.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py-flags-1.0.0.tar.gz (22.7 kB view hashes)

Uploaded Source

Built Distribution

py_flags-1.0.0-py3-none-any.whl (13.9 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page