Data types
This chapter describes the mapping of data types between Python and Tango.
Tango has more data types than Python which is more dynamic. The input and output values of the commands are translated according to the array below. Note that the numpy type is used for the input arguments. Also, it is recommended to use numpy arrays of the appropiate type for output arguments as well, as they tend to be much more efficient.
For scalar types (SCALAR)
Tango data type |
Python 2.x type |
Python 3.x type (New in PyTango 8.0) |
---|---|---|
DEV_VOID |
No data |
No data |
DEV_BOOLEAN |
||
DEV_SHORT |
||
DEV_LONG |
||
DEV_LONG64 |
|
|
DEV_FLOAT |
||
DEV_DOUBLE |
||
DEV_USHORT |
||
DEV_ULONG |
||
DEV_ULONG64 |
|
|
DEV_STRING |
|
|
DEV_ENCODED (New in PyTango 8.0) |
sequence of two elements: |
sequence of two elements: |
DEV_ENUM (New in PyTango 9.0) |
|
|
For array types (SPECTRUM/IMAGE)
Tango data type |
ExtractAs |
Data type (Python 2.x) |
Data type (Python 3.x) (New in PyTango 8.0) |
---|---|---|---|
DEVVAR_CHARARRAY |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_SHORTARRAY or (DEV_SHORT + SPECTRUM) or (DEV_SHORT + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_LONGARRAY or (DEV_LONG + SPECTRUM) or (DEV_LONG + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_LONG64ARRAY or (DEV_LONG64 + SPECTRUM) or (DEV_LONG64 + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|
||
Tuple |
|
||
DEVVAR_FLOATARRAY or (DEV_FLOAT + SPECTRUM) or (DEV_FLOAT + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_DOUBLEARRAY or (DEV_DOUBLE + SPECTRUM) or (DEV_DOUBLE + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_USHORTARRAY or (DEV_USHORT + SPECTRUM) or (DEV_USHORT + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_ULONGARRAY or (DEV_ULONG + SPECTRUM) or (DEV_ULONG + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|||
Tuple |
|||
DEVVAR_ULONG64ARRAY or (DEV_ULONG64 + SPECTRUM) or (DEV_ULONG64 + IMAGE) |
Numpy |
|
|
Bytes |
|||
ByteArray |
|||
String |
|
||
List |
|
||
Tuple |
|
||
DEVVAR_STRINGARRAY or (DEV_STRING + SPECTRUM) or (DEV_STRING + IMAGE) |
sequence< |
sequence< |
|
DEV_LONGSTRINGARRAY |
sequence of two elements:
|
sequence of two elements:
|
|
DEV_DOUBLESTRINGARRAY |
sequence of two elements:
|
sequence of two elements:
|
For SPECTRUM and IMAGES the actual sequence object used depends on the context
where the tango data is used, and the availability of numpy
.
for properties the sequence is always a
list
. Example:>>> import tango >>> db = tango.Database() >>> s = db.get_property(["TangoSynchrotrons"]) >>> print type(s) <type 'list'>
- for attribute/command values
numpy.ndarray
if PyTango was compiled withnumpy
support (default) andnumpy
is installed.list
otherwise
Pipe data types
Pipes require different data types. You can think of them as a structured type.
A pipe transports data which is called a blob. A blob consists of name and a list of fields. Each field is called data element. Each data element consists of a name and a value. Data element names must be unique in the same blob.
The value can be of any of the SCALAR or SPECTRUM tango data types (except DevEnum).
Additionally, the value can be a blob itself.
In PyTango, a blob is represented by a sequence of two elements:
blob name (str)
data is either:
sequence (
list
,tuple
, or other) of data elements where each element is adict
with the following keys:name (mandatory): (str) data element name
value (mandatory): data (compatible with any of the SCALAR or SPECTRUM data types except DevEnum). If value is to be a sub-blob then it should be sequence of [blob name, sequence of data elements] (see above)
dtype (optional, mandatory if a DevEncoded is required): see Data type equivalence. If dtype key is not given, PyTango will try to find the proper tango type by inspecting the value.
a
dict
where key is the data element name and value is the data element value (compact version)
When using the compact dictionary version note that the order of the data elements
is lost. If the order is important for you, consider using
collections.OrderedDict
instead.
Also, in compact mode it is not possible to enforce a specific type. As a
consequence, DevEncoded is not supported in compact mode.
The description sounds more complicated that it actually is. Here are some practical examples of what you can return in a server as a read request from a pipe:
import numpy as np
# plain (one level) blob showing different tango data types
# (explicity and implicit):
PIPE0 = \
('BlobCase0',
({'name': 'DE1', 'value': 123,}, # converts to DevLong64
{'name': 'DE2', 'value': np.int32(456),}, # converts to DevLong
{'name': 'DE3', 'value': 789, 'dtype': 'int32'}, # converts to DevLong
{'name': 'DE4', 'value': np.uint32(123)}, # converts to DevULong
{'name': 'DE5', 'value': range(5), 'dtype': ('uint16',)}, # converts to DevVarUShortArray
{'name': 'DE6', 'value': [1.11, 2.22], 'dtype': ('float64',)}, # converts to DevVarDoubleArray
{'name': 'DE7', 'value': numpy.zeros((100,))}, # converts to DevVarDoubleArray
{'name': 'DE8', 'value': True}, # converts to DevBoolean
)
)
# similar as above but in compact version (implicit data type conversion):
PIPE1 = \
('BlobCase1', dict(DE1=123, DE2=np.int32(456), DE3=np.int32(789),
DE4=np.uint32(123), DE5=np.arange(5, dtype='uint16'),
DE6=[1.11, 2.22], DE7=numpy.zeros((100,)),
DE8=True)
)
# similar as above but order matters so we use an ordered dict:
import collections
data = collections.OrderedDict()
data['DE1'] = 123
data['DE2'] = np.int32(456)
data['DE3'] = np.int32(789)
data['DE4'] = np.uint32(123)
data['DE5'] = np.arange(5, dtype='uint16')
data['DE6'] = [1.11, 2.22]
data['DE7'] = numpy.zeros((100,))
data['DE8'] = True
PIPE2 = 'BlobCase2', data
# another plain blob showing string, string array and encoded data types:
PIPE3 = \
('BlobCase3',
({'name': 'stringDE', 'value': 'Hello'},
{'name': 'VectorStringDE', 'value': ('bonjour', 'le', 'monde')},
{'name': 'DevEncodedDE', 'value': ('json', '"isn\'t it?"'), 'dtype': 'bytes'},
)
)
# blob with sub-blob which in turn has a sub-blob
PIPE4 = \
('BlobCase4',
({'name': '1DE', 'value': ('Inner', ({'name': '1_1DE', 'value': 'Grenoble'},
{'name': '1_2DE', 'value': ('InnerInner',
({'name': '1_1_1DE', 'value': np.int32(111)},
{'name': '1_1_2DE', 'value': [3.33]}))
})
)},
{'name': '2DE', 'value': (3,4,5,6), 'dtype': ('int32',) },
)
)
DevEnum pythonic usage
When using regular tango DeviceProxy and AttributeProxy DevEnum is treated just like in cpp tango (see enumerated attributes for more info). However, since PyTango >= 9.2.5 there is a more pythonic way of using DevEnum data types if you use the high level API, both in server and client side.
In server side you can use python enum.IntEnum
class to deal with
DevEnum attributes (here we use type hints, see Use Python type hints when declaring a device, but we can also set
dtype=Noon
when defining the attribute - see earlier versions of this documentation):
import time
from enum import IntEnum
from tango.server import Device, attribute, command
class Noon(IntEnum):
AM = 0 # DevEnum's must start at 0
PM = 1 # and increment by 1
class DisplayType(IntEnum):
ANALOG = 0 # DevEnum's must start at 0
DIGITAL = 1 # and increment by 1
class Clock(Device):
display_type = DisplayType.ANALOG
@attribute
def time(self) -> float:
return time.time()
@attribute(max_dim_x=9)
def gmtime(self) -> tuple[int]:
return time.gmtime()
@attribute
def noon(self) -> Noon:
time_struct = time.gmtime(time.time())
return Noon.AM if time_struct.tm_hour < 12 else Noon.PM
@attribute
def display(self) -> DisplayType:
return self.display_type
@display.setter
def display(self, display_type: int):
# note that we receive an integer, not an enum instance,
# so we have to convert that to an instance of our enum.
self.display_type = DisplayType(display_type)
@command(dtype_in=float, dtype_out=str)
def ctime(self, seconds):
"""
Convert a time in seconds since the Epoch to a string in local time.
This is equivalent to asctime(localtime(seconds)). When the time tuple
is not present, current time as returned by localtime() is used.
"""
return time.ctime(seconds)
@command
def mktime(self, tupl: tuple[int]) -> float:
return time.mktime(tuple(tupl))
if __name__ == "__main__":
Clock.run_server()
On the client side you can also use a pythonic approach for using DevEnum attributes:
import sys
import tango
if len(sys.argv) != 2:
print("must provide one and only one clock device name")
sys.exit(1)
clock = tango.DeviceProxy(sys.argv[1])
t = clock.time
gmt = clock.gmtime
noon = clock.noon
display = clock.display
print(t)
print(gmt)
print(noon, noon.name, noon.value)
if noon == noon.AM:
print("Good morning!")
print(clock.ctime(t))
print(clock.mktime(gmt))
print(display, display.name, display.value)
clock.display = display.ANALOG
clock.display = "DIGITAL" # you can use a valid string to set the value
print(clock.display, clock.display.name, clock.display.value)
display_type = type(display) # or even create your own IntEnum type
analog = display_type(0)
clock.display = analog
print(clock.display, clock.display.name, clock.display.value)
clock.display = clock.display.DIGITAL
print(clock.display, clock.display.name, clock.display.value)
Example output:
$ python client.py test/clock/1
1699433430.714272
[2023 11 8 8 50 30 2 312 0]
0 AM 0
Good morning!
Wed Nov 8 09:50:30 2023
1699429830.0
0 ANALOG 0
1 DIGITAL 1
0 ANALOG 0
1 DIGITAL 1