Data Types

SNAPpy supports many of the standard Python data types.

Note

You can use SNAPpy’s type() function to identify a variable’s type.

NoneType

None is a valid value in Python, as the only entity of type NoneType. Comparisons of a None value as if it were a Boolean will return False:

n = None

if n:
    print("This will never print.")

Setting string or byte list variables to None will release that buffer for use elsewhere.

Integer

SNAPpy integers are 16-bit signed values ranging from -32768 to 32767. If you add 1 to 32767, you will get -32768. You can specify integers using decimal notation or hexadecimal notation:

i = 0x1c2c

Normal Python mathematical operations apply to integers:

a = 39 + 3         # a = 42
a += 5             # a = 47
s = 48 - 6         # s = 42
s -= 5             # s = 37
m = 6 * 7          # m = 42
m *= 3             # m = 126
d = 551 / 13       # d = 42
d /= 8             # d = 5
r = 757 % 15       # r = 42
r %= 10            # r = 2
e = 13482 & 20311  # e = 1026: 00110100,10101010 & 01001111,01010111 = 00000100,00000010
e &= -1286         # e = 2:    00000100,00000010 & 11111010,11111010 = 00000000,00000010
o = 10 | 7         # o = 15:   00000000,00001010 | 00000000,00000111 = 00000000,00001111
o |= 240           # o = 255:  00000000,00001111 | 00000000,11110000 = 00000000,11111111
l = 10 << 2        # l = 40
l = 16384 << 1     # l = -32768
h = 32767 >> 2     # h = 8191
h = -32768 >> 2    # h = -8192 !!! Might not be as expected !!!

SNAPpy does not generate an error if you divide by zero. The result of that division will be zero:

42 / 0 = 0

Note that the division is integer division, taking the floor value of the division:

999 / 1000 = 0

The Python floor (//) operator is not implemented. When negative numbers are involved as either the divisor or dividend, the value will be the quotient value closest to zero. For example

-20 / 3 = -6
-20 / -3 = 6
20 / 3 = 6
20 / -3 = -6

This is different from the implementation of the floor operator in pure Python, where -20 // 3 = -7, as it takes the next lowest integer rather than the integer with the lowest absolute value.

The modulo (%) operator returns the remainder after an integer division. Again, the implementation varies from the modulo implementation in pure Python. In SNAPpy, the values to expect are:

-20 % 3 = -2
-20 % -3 = -2
20 % 3 = 2
20 % -3 = 2.

Pure Python gives different results:

-20 % 3 = 1
-20 % -3 = -2
20 % 3 = 2
20 % -3 = -1.

Bitwise and (&) and bitwise or (|) operators function as expected, as does the left-shift (<<) operator. Beware when using the right-shift (>>) operator on an integer with the high bit set, though, as the high bit is what marks the number as negative, and after the shift that bit will be reapplied. The Python power (**) operator is not implemented.

String

Strings are not null-terminated in SNAPpy, so they can contain any of the 256 possible values for each character, including (\x00). SNAPpy treats static and dynamic strings differently:

Static string

An immutable constant in your code. Each static string has a maximum size of 255 bytes.

Dynamic string

Created when you assign a value to a string while a script is running or attempt to reassign a value to a string declared outside a function. The maximum size of dynamic strings (up to 255 bytes) is platform specific. See SNAP Modules for more information.

Strings in SNAPpy (as in Python) are immutable, so you cannot change any characters within the string after it has been created. In order to “modify” a string, you must perform slicing operations on the string, creating a new dynamic string in the process.

Note

To manage all these dynamic strings, SNAPpy uses a collection of string buffers. You will need to understand Memory Management to make the best use of these resources. See SNAP Modules for more information about resource limitations.

You can use the in operator to determine whether a string contains a substring and the for statement to iterate through characters in a string:

for c in myString:
    if isSpecial(c):
        print("Look what I found: ", c)

A non-standard feature of SNAPpy strings is that if a string variable contains the name of a valid SNAPpy function, you can invoke the variable name as if it were the function. In the following example, passing any string matching the name of a valid function loaded on the device (such as "random", "getLq", or even a user-defined one) would cause that named function to be invoked immediately:

def runArbitrary(function):
    return function()

Function

In SNAPpy, as in Python, a function name is essentially a variable that points to a function. As such, another variable can be assigned to point to that function, too:

@setHook(HOOK_STARTUP)
def onStartup():
    global myRandom
    myRandom = random

def odd():
    return random() & 4094  # Clear last bit

def even():
    return random() | 1  # Set last bit

def setRandomMode(newMode):
    global myRandom
    if newMode == 1:
        myRandom = odd
    elif newMode == 2:
        myRandom = even
    else:
        myRandom = random

After a call to setRandomMode() to specify which character of random numbers should be returned, any future calls to myRandom() will return either an odd random number, an even random number, or an unspecified random number.

Note that saying myRandom = odd is an assignment of the odd() function to the myRandom variable. This is very different from saying myRandom = odd(), which would assign the return value of a call to the odd() function to the myRandom variable.

Both user-defined functions and built-in functions can be assigned to your variables. You then call the function by invoking the variable name followed by parentheses (which should contain any arguments the function requires). The function can be invoked directly from another function on the same device or by Remote Procedure Call (direct or multicast) from another device, which establishes the ability to have one multicast call cause different devices to run different functions.

Both public and non-public functions are only limited by available flash space. Testing has confirmed that more than 500 functions can be available on a device.

Boolean

A Boolean has a value of either True or False, which is case-sensitive. Comparisons of Booleans can be direct:

b = True
if b:
    print("This will print.")

if b == True:
    print("This will also print.")

Tuple

A tuple is an ordered, read-only container of data elements. Not only is the tuple immutable, but its contents are, too. You cannot even change any of the bytes in a byte list that is contained within a tuple. There are restrictions on printing of nested tuples. See the section on Printing below for more details.

Elements in a tuple can be of almost 1 any type available in SNAPpy, including nested tuples:

myTuple = (None, True, 2, "Three", ("Four", "in", "this", "tuple"), [5, 10, 15, 20, 25])

You can access tuple elements by stepping through the tuple with a for loop or by selecting individual elements. In the sample tuple above, myTuple[3] would be "Three" and myTuple[4][0] would be "Four".

You can use the in operator to determine whether a tuple contains an element and the for statement to iterate through elements in a tuple:

for element in myTuple:
    print(element)

Warning

You cannot pass a tuple as an argument in a Remote Procedure Call (RPC), though you can pass any tuple element.

Iterator

Iterators are generated using the xrange() function. Iterators defined in the global space of SNAPpy scripts are not supported. Typically, an iterator is not assigned to a variable but is used in-line:

for a in xrange(3):
    print(a)

However, it is possible to assign iterators to variables and pass them as parameters within a device:

def makeIterator(top):
    a = xrange(top)
    return sum(a)

def sum(anIterator):
    count = 0
    for a in anIterator:
        count += a
        return count

Warning

You cannot pass an iterator as an argument in an RPC.

Byte List

Byte lists provide some limited Python list functionality. A byte list is an ordered list of unsigned, one-byte integers. While byte lists and strings work from the same pool of buffers, the processing that you can perform on the data types varies. While strings in SNAPpy (as in Python) are immutable, byte list elements can be changed in place:

myList = [1, 2, 3, 4, 5]
myList[2] = 42  # Now list is [1, 2, 42, 4, 5]

This ability to modify a byte (or a slice of bytes) without having to rebuild the list allows for much faster processing than trying to perform the same functions using strings, and it does not require available buffers for processing the slicing.

Beginning in SNAP 2.7, the += operator is supported on elements within a byte list:

myList = [1, 2, 3, 4, 5]
myList[4] += 1  # Now list is [1, 2, 3, 4, 6]

You can define byte lists specifying literals or variables in square brackets:

myList = [1, 2, 3]
myInt = 4
myList = myList + [myInt] + [myInt, myInt]  # Now list is [1, 2, 3, 4, 4, 4]

You can also build up lists using list multiplication:

myList = [0] * 10  # Now list is [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

It is easy to convert between byte lists and strings:

myList = ["Byte list"]  # Now list is [66, 121, 116, 101, 32, 108, 105, 115, 116]
myString = chr(myList)  # Now myString = "Byte list"
toStr = str(myList)     # Now toStr = "[66,121,116,101,32,108,105,115,116]"

You can step through byte lists using while or for loops, and you can also use the in operator to determine whether a number is in your list. The del operator can be used to delete an element or range of elements from a list (by position) or to delete the entire list:

myList = [1, 2, 3, 4, 5]
del myList[2]    # myList = [1, 2, 4, 5]
del myList[1:3]  # myList = [1, 5]
del myList[0:2]  # myList = [], an empty list
del myList  # myList is now an unknown variable. type(myList) returns 31

Beginning in SNAP 2.7, del works with negative index values:

myList = [1, 2, 3, 4, 5]
del myList[-1] # myList = [1, 2, 3, 4]

In order to preserve RAM, SNAP firmware makes some decisions about where it stores global variables, locating them in flash memory rather than RAM when a device boots. If you will be modifying individual entries in a globally defined byte list, you need to be working with the variable in RAM rather than flash, so you must force SNAPpy to make a copy of the variable (consuming a buffer) first. The easiest way to do that is to slice the list into a new list, and the most efficient way to ensure that this happens once (and only once) is to include it in your hooked startup code:

myList = [1, 2, 3, 4, 5]

@setHook(HOOK_STARTUP)
def onStartup():
    global myList
    myList = myList[:]

Byte lists that are defined at run-time are limited in size by the available stack size in SNAP, which can vary based on the current state of your call structure and how many parameters have been passed, etc. This means that if you are building a larger list on-the-fly as a local variable, you may have to break the list into several chunks and add them together. (The SNAPpy data stack is on the order of 64 variables deep.)

Warning

You cannot pass a byte list as an argument in an RPC call; however, you can use chr() to convert the byte list to a string which can be passed.

Unsupported

SNAPpy does not support user-defined classes or the following Python data types:

  • float – A float is a floating-point number with a decimal part.

  • long – A long is an integer with arbitrary length (potentially exceeding the range of an int).

  • complex – A complex is a number with an imaginary component.

  • list – A list is an ordered collection of elements, excepting byte lists as described above.

  • dict – A dict is an unordered collection of pairs of keyed elements.

  • set – A set is an unordered collection of unique elements.

Note

While unsupported types cannot be used in SNAPpy scripts, they can still be used in a SNAPconnect application.

1

Tuples cannot contain iterators.