Boolean and Enumerated Data¶
If a property or method argument can take exactly two values,
of which one can be interpreted in the affirmative, use Python
bool data types to represent this. Be permissive in what you accept
False, in order to be consistent with Python conventions
for truthy and falsey values. This can be accomplished using the
bool function to convert to Booleans, and is done implicitly by
If a property has more than two permissible values, or the two allowable
values are not naturally interpreted as a Boolean (e.g.: positive/negative,
AC/DC coupling, etc.), then consider using an
enum. The latter is useful in for wrapping integer values that
are meaningful to the device.
For example, if an instrument can operate in AC or DC mode, use an enumeration like the following:
class SomeInstrument(Instrument): # Define as an inner class. class Mode(Enum): """ When appropriate, document the enumeration itself... """ #: ...and each of the enumeration values. ac = "AC" #: The "#:" notation means that this line documents #: the following member, SomeInstrument.Mode.dc. dc = "DC" # For SCPI-like instruments, enum_property # works well to expose the enumeration. # This will generate commands like "MODE AC" # and "MODE DC". mode = enum_property( name=":MODE", enum=SomeInstrument.Mode, doc=""" And here is the docstring for this property """ ) # To set the mode is now straightforward. ins = SomeInstrument.open_somehow() ins.mode = ins.Mode.ac
Note that the enumeration is an inner class, as described below in Associated Types.
Object Oriented Design¶
Many instrument classes have associated types, such as channels and axes, so that these properties of the instrument can be manipulated independently of the underlying instrument:
>>> channels = [ins1.channel, ins2.channel]
Here, the user of
channels need not know or care that the two
channels are from different instruments, as is useful for large
installations. This lets users quickly redefine their setups
with minimal code changes.
To enable this, the associated types should be made inner classes
that are exposed using
class SomeInstrument(Instrument): # If there's a more appropriate base class, please use it # in preference to object! class Channel: # We use a three-argument initializer, # to remember which instrument this channel belongs to, # as well as its index or label on that instrument. # This will be useful in sending commands, and in exposing # via ProxyList. def __init__(self, parent, idx): self._parent = parent self._idx = idx # define some things here... @property def channel(self): return ProxyList(self, SomeInstrument.Channel, range(2))
This defines an instrument with two channels, having labels
By using an inner class, the channel is clearly associated with the instrument,
and appears with the instrument in documentation.
Since this convention is somewhat recent, you may find older code that uses a style more like this:
class _SomeInstrumentChannel: # stuff class SomeInstrument(Instrument): @property def channel(self): return ProxyList(self, _SomeInstrumentChannel, range(2))
This can be redefined in a backwards-compatible way by bringing the channel class inside, then defining a new module-level variable for the old name:
class SomeInstrument(Instrument): class Channel: # stuff @property def channel(self): return ProxyList(self, _SomeInstrumentChannel, range(2)) _SomeInstrumentChannel = SomeInstrument.Channel