Data structures | Algorand Developer Portal

Data structures

In terms of data structures, Algorand Python currently provides support for composite data types and arrays.

In a restricted and costly computing environment such as a blockchain application, making the correct choice for data structures is crucial.

All ARC-4 data types are supported, and initially were the only choice of data structures in Algorand Python 1.0, other than statically sized native Python tuples. However, ARC-4 encoding is not an efficient encoding for mutations, additionally they were restricted in that they could only contain other ARC-4 types.

As of Algorand Python 2.7, two new array types were introduced algopy.Array, a mutable array type that supports statically sized native and ARC-4 elements and algopy.ImmutableArray that has an immutable API and supports dynamically sized native and ARC-4 elements.

Mutability vs Immutability

A value with an immutable type cannot be modified. Some examples are UInt64, Bytes, tuple and typing.NamedTuple.

Aggregate immutable types such as tuple or ImmutableArray provide a way to produce modified values, this is done by returning a copy of the original value with the specified changes applied e.g.

import typing

import algopy

# update a named tuple with _replace
class MyTuple(typing.NamedTuple):
    foo: algopy.UInt64
    bar: algopy.String

tup1 = MyTuple(foo=algopy.UInt64(12), bar=algopy.String("Hello"))

# this does not modify tup1
tup2 = tup1._replace(foo=algopy.UInt64(34))
assert tup1.foo != tup2.foo

# update immutable array by appending and reassigning
arr = algopy.ImmutableArray[MyTuple]()
arr = arr.append(tup1)
arr = arr.append(tup2)

Mutable types allow direct modification of a value and all references to this value are able to observe the change e.g.

import algopy

# both my_arr and my_arr2 both point to the same array
my_arr = algopy.Array[algopy.UInt64]()
my_arr2 = my_arr
my_arr.append(algopy.UInt64(12))
assert my_arr.length == 1
assert my_arr2.length == 1
my_arr2.append(algopy.UInt64(34))
assert my_arr2.length == 2
assert my_arr.length == 2

Static size vs Dynamic size

A static sized type is a type where its total size in memory is determinable at compile time, for example UInt64 is always 8 bytes of memory. Aggregate types such as tuple, typing.NamedTuple, arc4.Struct, and arc4.Tuple are static size if all their members are also static size e.g. tuple[UInt64, UInt64] is static size as it contains two static sized members.

Any type where its size is not statically defined is dynamically sized e.g. Bytes, String, tuple[UInt64, String], and Array[UInt64] are all dynamically sized.

Size constraints

All bytes on the AVM stack cannot exceed 4096 bytes in length, this means all arrays and structs cannot exceed this size. Boxes are an exception to this, the contents of a box can be up to 32k bytes. However loading this entire box into a variable is not possible as it would exceed the AVM limit of 4096 bytes. However Puya will support reading and writing parts of a box

import typing

from algopy import Box, FixedArray, Struct, UInt64, arc4, size_of
class BigStruct(Struct):
    count: UInt64  # 8 bytes
    large_array: FixedArray[UInt64, typing.Literal[512]]  # 4096 bytes
class Contract(arc4.ARC4Contract):
    def __init__(self) -> None:
        self.box = Box(BigStruct)
        self.box.create()
    @arc4.abimethod()
    def read_box_fails(self) -> UInt64:
        assert size_of(BigStruct) == 4104
        big_struct = self.box.value  # this fails to compile because size_of(BigStruct)
        assert big_struct.count > 0, ""

Algorand Python composite types

tuple

This is a regular python tuple

typing.NamedTuple

Struct

arc4.Tuple

arc4.Struct

Algorand Python array types

algopy.FixedArray

algopy.Array

algopy.ReferenceArray

import algopy
class SomeContract(algopy.arc4.ARC4Contract):
    @algopy.arc4.abimethod()
    def get_array(self) -> algopy.ImmutableArray[algopy.UInt64]:
        arr = algopy.ReferenceArray[algopy.UInt64]()
        # modify arr as required
        ...
        # return immutable copy
        return arr.freeze()

algopy.ImmutableArray

algopy.arc4.DynamicArray / algopy.arc4.StaticArray

Tips