# Python containers conversion to Fish

**URL:** <https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388>\
**Category:** Wish List\
**Tags:** python\
**Created:** [March 29, 2024, 5:16pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388 "2024-03-29T17:16:10Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Theophile](https://yyz1.discourse-cdn.com/flex031/user_avatar/forum.itascainternational.com/theophile/32/299_2.png) [@Theophile](https://forum.itascainternational.com/u/Theophile)\
**Post date:** [March 29, 2024, 5:16pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/1 "2024-03-29T17:16:10Z")

</div>

Hi everyone!

We are extensively using the Python API in our 3DEC computations, so first of all, thanks for that!

As of today (and at least in 3DEC), conversion of Python containers to Fish objects are not supported, except for length 2 `list`/`tuple`/`numpy.arrays` (+ `vec.vec2`) that would convert to `vector2` and length 3 of the same objects (+ `vec.vec3`) that would convert to `vector3`.

Interestingly, conversion is more flexible on the other way around:

| Fish | Python |
| --- | --- |
| list (of any length) | tuple |
| map | dict |
| vector2 | vec.vec2 |
| vector3 | vec.vec3 |
| array | _not supported_ |
| matrix | _not supported_ |
| tensor | _not supported_ |

I have no idea of how much work it would require, but would it be possible to complete the conversion of containers between Python and Fish in a near future update?

Maybe not all conversions, but being able to convert `tuples` and `numpy.arrays` from Python to Fish, and Fish arrays to `numpy.arrays` in Python would be great.

Thanks a lot!

Regards

Théophile

---

<div class="post-metadata">

**Author:** ![Huy](https://avatars.discourse-cdn.com/v4/letter/h/c5a1d2/32.png) [@Huy](https://forum.itascainternational.com/u/Huy)\
**Post date:** [April 2, 2024, 8:51am UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/2 "2024-04-02T08:51:02Z")

</div>

Hello Théophile,  
We added the conversions for more containers in the recent version (list python → list fish, dict python → map fish).  
We can add  
matrix \<=\> numpy array  
tensor \<=\> vec.tens3  
For array, I think it’s more like multi-dimensional lists.

---

<div class="post-metadata">

**Author:** ![Theophile](https://yyz1.discourse-cdn.com/flex031/user_avatar/forum.itascainternational.com/theophile/32/299_2.png) [@Theophile](https://forum.itascainternational.com/u/Theophile)\
**Post date:** [April 2, 2024, 1:01pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/3 "2024-04-02T13:01:40Z")

</div>

Hi @Huy !

Ok, thanks.

As of 3DEC versions 7.00.161 and 9.00.167, setting a Fish list from a Python’s one still raises a `ValueError: unknown type in conversion from PyObject to QVariant tuple`.

In the meantime, I wrote Python functions that I give at the end of this post in case it could be of any help to some users. They deal with the following Python to Fish conversions:

- `list`/`tuple` to `list`
- `dict` to `map`
- `numpy.ndarray` to `array`

Notice it is only a temporary solution until your native implementation since it is underoptimized (e.g., 1 s for instantiating à 100,000 elements array from Python to Fish versus 0.5 ms in pure Python). Besides, since it uses a bypass through strings, the float format precision might falsify the values in Fish.

Cheers!

Regards

Théophile

PS: the code

```python
"""
Temporary solution to pass containers to Fish

Warning
-------
Just a workaround until container conversion is natively dealt with through
Itasca's Python API. This workaround is underoptimized, e.g., a random 
(100, 1000)-shaped array takes 0.5ms to run in Python console, and
conversion to Fish takes ~1s (depends of course on the machine it's running on,
but it's the 2000x ratio that matters).

"""

import itasca as it

import numpy as np

def format_fish_list(iterator, transformers={}):
    """
    Formatting a Python iterator to a Fish list
    
    Parameters
    ----------
    iterator: iterator (preferably list or tuple)
        The sequence to convert to Fish list
        
        Note
        ----
        Nested iterators are supported
    transformers: dict
        Custom type-to-string formatters with (key, val) = (type to format,
        formatting function), see examples
        
    Returns
    -------
    out: str
        The formatted iterator as a Fish list constructor
        
    Examples
    --------
    >>> test = (1, 'foo', 'bar', ('nested', True), 3.2548e-10)
    >>> format_fish_list(test)
    'list.seq(1, "foo", "bar", list.seq("nested", true), 3.2548e-10)'
    
    Custom converters
    
    >>> test = (99, {'a': 1, 'b': 2})
    >>> format_fish_list(test)
    KeyError: <class 'dict'>
    >>> def my_dict_conv(d):
    ... return format_fish_list(d.values())
    >>> format_fish_list(test, {dict: my_dict_conv})
    >>> test = (99, {'a': 1, 'b': 2})
    'list.seq(99, list.seq(1, 2))'
    
    """
    transform = {
        bool: lambda x: str(x).lower(),
        int: lambda x: str(x),
        float: lambda x: str(x),
        list: format_fish_list,
        str: lambda x: f'"{x}"',
        tuple: format_fish_list,
        }
    transform.update(transformers)
    items = ', '.join([
        transform[type(item)](item)
        for item in iterator
        ])
    return f'list.seq({items})'

def format_fish_array(arr):
    """
    Formatting a numpy array to a Fish array
    
    Parameters
    ----------
    arr: np.ndarray
        The array to format
        
    Returns
    -------
    out: str
        The formatted array as a Fish array constructor
        
    Examples
    --------
    >>> import numpy as np
    >>> test = np.array(((1,2,3), (4,5,6)))
    >>> format_fish_array(test)
    'array(list.seq(1, 4, 2, 5, 3, 6), 2, 3)'

    """
    items = arr.flatten('F').tolist()
    shape = ', '.join([str(n) for n in arr.shape])
    return f'array({format_fish_list(items)}, {shape})'

def format_fish_map(dictionary):
    """
    Formatting a dictionary a Fish map
    
    Parameters
    ----------
    dictionary: dict
        The dictionary to format
        
    Returns
    -------
    out: str
        The formatted dict as a Fish map constructor
        
    Examples
    --------
    >>> test = {'hello': 'world', -62: 1.e10, True: False}
    >>> format_fish_map(test)
    'map(list.seq("hello", -62, true), list.seq("world", 10000000000.0, false))'
    
    """
    keys, values = zip(*dictionary.items())
    return f'map({format_fish_list(keys)}, {format_fish_list(values)})'
    
def format_fish_container(
    container, var_name=None, instantiate=False, transformers={}
    ):
    """
    Formatting a Python container to a Fish constructor
    
    Parameters
    ----------
    container: list/tuple/dict/np.ndarray
        The container to set in Fish
    var_name: str
        The name of the Fish variable for storing the container.
        If None, the formatted container has no associated variable.
    instantiate: bool
        Whether the container should be instantiated on the fly in Fish
        (var_name must not be None).
    transformers: dict
        Custom type-to-string formatters with (key, val) = (type to format,
        formatting function), see doc from format_fish_list()
        
    Examples
    --------
    List/tuple
    
    >>> test = (1, -15., 'foo', ('nested', True), 3.2548e-10)
    >>> format_fish_container(test)
    'list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
    >>> format_fish_container(demo_seq, 'test')
    'test=list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
    
    Setting variable on-the-fly
    
    >>> format_fish_container(demo_seq, 'test', instantiate=True)
    'test=list.seq(1, -15.0, "foo", list.seq("nested", true), 3.2548e-10)'
    >>> it.fish.get('test')
    (1, -15.0, 'foo', ('nested', True), 3.2548e-10)
    
    Array
    
    >>> test_arr = np.array(((1.,2.,3.),(4.,5.,6.)))
    >>> format_fish_container(test_arr, 'test_arr', instantiate=True)
    'test_arr=array(list.seq(1.0, 4.0, 2.0, 5.0, 3.0, 6.0), 2, 3)'
    
    Dict
    
    >>> test_dict = {'hey': 'yo', -62: 1.e3, True: False}
    >>> format_fish_container(test_dict, 'test_dict', instantiate=True)
    'test_dict=map(list.seq("hey", -62, true), list.seq("yo", 1000.0, false))'

    """
    if isinstance(container, np.ndarray):
        items = format_fish_array(container)
    elif isinstance(container, dict):
        items = format_fish_map(container)
    else:
        items = format_fish_list(container, transformers)
    if var_name is not None:
        items = f'{var_name}={items}'
        if instantiate:
            it.command(f'[{items}]')
    else:
        assert not instantiate, 'Cannot instantiate with no var_name'
    return items

if __name__ == ' __main__':
    # List/tuple
    test = (1, -15., 'foo', ('nested', True), 3.2548e-10)
    print(format_fish_container(test))
    print(format_fish_container(test, 'test'))
    assert not it.fish.has('test')
    format_fish_container(test, 'test', instantiate=True)
    assert it.fish.has('test')
    assert test==it.fish.get('test')
    # Array: test with a silly formatting
    test_arr = np.array((((1,),(2,),(3,)),((4,),(5,),(6,))))
    format_fish_container(test_arr, 'test_arr', instantiate=True)
    assert it.fish.has('test_arr')
    # Dict
    test_dict = {'hey': 'yo', -62: 1.e3, True: False}
    format_fish_container(test_dict, 'test_dict', instantiate=True)
    assert test_dict==it.fish.get('test_dict')

```

---

<div class="post-metadata">

**Author:** ![Huy](https://avatars.discourse-cdn.com/v4/letter/h/c5a1d2/32.png) [@Huy](https://forum.itascainternational.com/u/Huy)\
**Post date:** [April 2, 2024, 1:49pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/4 "2024-04-02T13:49:39Z")

</div>

You can check maybe the next subversion.  
Note that the fish array is not homogeneous as ndarray. So I think ndarray should be equivalent to fish matrix

---

<div class="post-metadata">

**Author:** ![Theophile](https://yyz1.discourse-cdn.com/flex031/user_avatar/forum.itascainternational.com/theophile/32/299_2.png) [@Theophile](https://forum.itascainternational.com/u/Theophile)\
**Post date:** [April 2, 2024, 2:38pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/5 "2024-04-02T14:38:20Z")

</div>

Oh, ok, thanks for the hint.

In previous versions of 3DEC, I think arrays stored homogeneous types.  
Converting to a matrix would restrict to 2-dimensional arrays, but it’s true that it’s their most common use.

Cheers!

---

<div class="post-metadata">

**Author:** ![vl.diachenko](https://avatars.discourse-cdn.com/v4/letter/v/9dc877/32.png) [@vl.diachenko](https://forum.itascainternational.com/u/vl.diachenko)\
**Post date:** [September 4, 2025, 3:10pm UTC](https://forum.itascainternational.com/t/python-containers-conversion-to-fish/1388/6 "2025-09-04T15:10:07Z")

</div>

Any updates?

I’m trying to pass an array to a FISH function:

```auto
fish define import_data(arr)
    loop foreach local value arr
        io.out("Processing " + string(value))
    endloop
end

```

```python
import itasca as it
data = [1, 2, 3, 4, 5]
it.fish.call_function("import_data", (data,))

```

But I get this error:

```python
AttributeError Traceback (most recent call last)
AttributeError: Slot 'CommManager::_dispatch(PyObject)' not found.
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[55], line 1
----> 1 it.fish.call_function("import_data", (data,))

ValueError: Cannot iterate over objects of type Integer.

```

FLAC3D 9.4.32.
