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:
- SEQUENCE — An ordered collection of named components. Every component that is present is encoded, in order. This is how composite types like a property reference or a date-time pair are built.
- CHOICE — A set of named alternatives where the value is exactly one of them. Only the selected alternative is encoded, and the decoder uses the context tag to learn which one it received. BACnet uses CHOICE for types that can take one of several forms — a timestamp expressed as a time, a sequence number, or a date-time, for example.
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:
- A
_choiceattribute — an optional string,Noneby default, that records which single element is currently selected.Sequencehas no equivalent because all of its present elements are valid simultaneously. - A constrained
__init__— when you construct aChoice, it accepts exactly one element. Passing more than one raises an error before the object is built, and the chosen name is validated against_elements. - Single-element encode/decode — where
Sequenceencodes every non-empty element in order,Choiceencodes only the currently selected element. On decode, it reads the alternative indicated by the wire and sets_choiceaccordingly.
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, SequenceConstruction takes either a single-key dictionary or a single keyword argument naming the selected alternative. The verified behavior of the base Choice.__init__ is:
- Pass one element by keyword — for example
SomeChoice(time=...)— and that element becomes the active_choice. - Pass a dict with one key —
SomeChoice({"time": ...})— and it behaves the same way. - Pass another
Choiceinstance and it copies that instance's_choiceselection.
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
- "initialize one choice" at construction time. If you pass two or more elements — whether as two dict keys or two keyword arguments — the constructor raises a
RuntimeErrornaming the conflicting keys. A CHOICE is exactly one alternative; there is no "set both and let the encoder pick." Decide which alternative you mean and pass only that one. - "choice is not an element" errors. The name you select has to match a key in
_elements— that is, an alternative actually declared on the class. A typo in the element name, or assuming an alternative exists when the class doesn't declare it, raises anAttributeError. Check the class definition for the exact element names before constructing. - Expecting Sequence-style multi-field access. Because
Choiceinherits fromSequence, it can be tempting to treat it like one and read several fields. Only the selected element is meaningful; the others are not populated. Read_choiceto learn which alternative is set, then read that element. - Assuming the inheritance changes the wire format. The
class Choice(Sequence)relationship is an implementation detail of bacpypes3. It does not mean a CHOICE is encoded like a SEQUENCE on the wire. A CHOICE still encodes a single tagged alternative exactly as ASHRAE 135 specifies; the shared Python base class does not leak into the BACnet APDU.
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:
- BACpypes3 project repository — Joel Bender. The
constructeddata.pymodule is the normative source for theSequenceandChoicebase classes, the_elementsand_choiceattributes, and the constructor constraints described here. - ASHRAE Standard 135 — BACnet—A Data Communication Protocol for Building Automation and Control Networks. The normative reference for the SEQUENCE and CHOICE constructed data types and their encoding.
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.
Was this article helpful?
Related Articles
Why bacpypes3 Defines class TimeValue(Sequence)
Tools & Librariesbacpypes3 class Any: The Open BACnet Value Container
Tools & Librariesbacpypes3 Time Class: Why It's a Tuple of (Hour, Minute, Second, Hundredth)
Niagara / TridiumTridium Upgrade: Niagara Version Upgrade and Migration Guide
Need to do this remotely? SiteConduit provides Layer 2 access that preserves BACnet broadcasts — no BBMD needed for remote sessions. Join the waitlist.
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.