Why bacpypes3 Defines class Choice(Sequence)

Tools & Librariesbacpypes3ChoiceSequenceconstructed data
June 14, 2026|8 min read

In bacpypes3, class Choice(Sequence) means the Choice base class is implemented as a subclass of Sequence — it reuses the same metaclass machinery that collects named elements into the _elements dictionary, then constrains that machinery so exactly one element is active at a time. The active element name is stored in _choice, and the encode/decode logic processes only that single element instead of the whole list. This models the ASHRAE 135 CHOICE construct, where a value is exactly one of several named alternatives. You don't subclass Sequence yourself to get this—you subclass Choice, which is already wired for mutual exclusivity.

You're Reading the Source and Hit This Line

You open bacpypes3/constructeddata.py to understand how a BACnet type encodes, and you find:

class Choice(Sequence):
    ...

That looks backwards at first. A CHOICE and a SEQUENCE are different ASN.1 constructs — a SEQUENCE carries all of its named fields, a CHOICE carries exactly one. So why does the CHOICE class inherit from the SEQUENCE class instead of the other way around, or from a shared parent?

The short version: bacpypes3 already built all the element-collection plumbing on Sequence, and Choice needs that same plumbing. Rather than duplicate it, Choice extends Sequence and overrides the few behaviors that differ. The inheritance is about code reuse, not about a CHOICE being a kind of SEQUENCE in the protocol sense.

CHOICE vs SEQUENCE in ASHRAE 135

BACnet's data types are specified in ASHRAE Standard 135 using ASN.1 notation. Two of the constructed types matter here:

Both constructs need the same underlying capability: a way to declare named components, each with its own type and (usually) a context tag, and a way to walk those components during encode and decode. That shared capability is exactly what bacpypes3 puts on Sequence.

What the Inheritance Actually Gives Choice

Sequence in bacpypes3 is defined with a metaclass that, at class-creation time, scans the class for its declared components — both type-annotated attributes and class attributes whose values are element types — and records them in a class-level _elements dictionary mapping each element name to its type. Every Sequence subclass inherits this collection behavior, so a Choice subclass gets it for free.

On top of the inherited machinery, Choice adds and overrides:

So Choice isn't loosening Sequence's rules—it's tightening them. It takes "a collection of named elements" and adds "but only one may be set."

How _choice and _elements Relate

_elements is the static declaration: every alternative the CHOICE could hold, populated once by the metaclass when the class is defined. _choice is the runtime state: the name of the one alternative this particular instance currently holds. The constructor cross-checks the two — the name you select must already exist as a key in _elements, or construction fails. For a deeper walk through both attributes and how the metaclass fills them in, see bacpypes3 Choice, _choice, and _elements: Source and Usage.

Declaring and Constructing a Choice

You generally won't write class Choice(Sequence) yourself — that line is the library's base class. You subclass Choice and declare your alternatives the same way you'd declare Sequence components. The import path in current releases is:

from bacpypes3.constructeddata import Choice, Sequence

Construction takes either a single-key dictionary or a single keyword argument naming the selected alternative. The verified behavior of the base Choice.__init__ is:

For real, standard-compliant CHOICE definitions to copy from, read the BACnet base types in bacpypes3/basetypes.py — types such as the BACnet timestamp are declared there as Choice subclasses. Rather than reproduce a definition that may drift between releases, open the source in your installed package and match its pattern exactly. If you're building a full custom application around these types, the broader workflow is covered in BACpypes3: Custom BACnet Applications in Python.

Common Pitfalls

When to Dig Deeper or Escalate

If you're only trying to read or write a property and a CHOICE-typed value is involved, you rarely need to touch this base class — the higher-level client handles the construction for you. Go down to Choice internals when you're defining a custom type, debugging an encoding mismatch, or reconciling source you're reading against the encoded bytes on a capture.

Two cautions. First, bacpypes3 is under active development and internal attribute names or the exact metaclass behavior can change between releases — confirm what you see against the source of the version you actually have installed, not a blog post or an older tag. Second, if your encoded output doesn't match a reference capture, verify the problem is in the CHOICE selection and not in the context tag numbering of the enclosing type; those are separate failure modes that look similar from the application layer. When the discrepancy is in the standard's definition of a type rather than the library, the authoritative reference is ASHRAE Standard 135 itself. For library-specific questions that the source doesn't answer, the bacpypes3 project README and issue tracker are the right next stop.

Source Attribution

The technical guidance in this entry is informed by the following sources:

Class names, attribute names, and constructor behavior were read from the bacpypes3 source rather than reconstructed from memory; confirm against your installed release before relying on any internal detail.

bacpypes3ChoiceSequenceconstructed dataBACnet types

Was this article helpful?

Related Articles

Need to do this remotely? SiteConduit provides Layer 2 access that preserves BACnet broadcasts — no BBMD needed for remote sessions. Join the waitlist.

SC

SiteConduit Technical Team

Idea Networks Inc.

SiteConduit builds managed remote access for building automation. Our knowledge base is maintained by BAS professionals with hands-on experience deploying and troubleshooting BACnet, Niagara, Modbus, and Facility Explorer systems.