Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Toggle table of contents sidebar

# Python types

## Available wrappers

All major Python types are available as thin SystemVerilog wrapper classes. These can also be used as function parameters – see Python objects as arguments.

Available types include : `py_bool`, `py_int`, `py_float`, `py_str`, `py_bytes`, `py_tuple`, `py_list`, `py_dict`, `py_none`

## Instantiating compound Python types from SystemVerilog

Dictionaries can be initialized in the `py_dict` methods using the `create()` or `empty()` methods:

    import pystim_pkg::*;
    ...
    typedef pystim_pkg::pystim py;
    ...
    py_pair p0 = new(py::int_(-3), py::str_("three"));
    py_pair p1 = new(py::str_("foo"), py::float_(42.3));

    py_dict d = py_dict::create({p0, p1});
    py_dict empty_d = py_dict::create_empty();

    empty_d.put(py::str_("key"), py::int_(42));

Note

Dictionary keys must be of a string type.

A tuple of python objects can be instantiated using `create()` or `empty()` methods of `py_tuple`:

    py_tuple tuple = py_tuple::create({py::int_(15), py::none_(), py::str_("spam")});
    py_tuple empty_tup = py_tuple::create_empty();

    empty_tup.put(py::int_(42));

A list of python objects can be instantiated using `create()` or `empty()`

## Casting back and forth

In this kind of mixed code, it is often necessary to convert base SystemVerilog types to Python wrappers types, which can be done using `pystim_pkg::int_()`, `pystim_pkg::float_()`, `pystim_pkg::str_()`, `pystim_pkg::bytes_()`, `pystim_pkg::list_()`, `pystim_pkg::tuple_()`, `pystim_pkg::dict_()`, `pystim_pkg::none_()` and similar functions.

    int my_int = ...;
    py::py_int obj = py::int_(my_int);

The reverse direction uses the following syntax:

    py_object obj = ...;
    int my_int = obj.cast_int().get_value();

When conversion fails, both directions fails with the cast error.

## Accessing Python libraries from SystemVerilog

It is also possible to import objects defined in the Python standard library or available in the current Python environment (`sys.path`) and work with these in SystemVerilog. In this case the SystemVerilog object represents the pointer to the Python object.

Note

All allocated SystemVerilog python object handlers should be disposed with `delete()` method at the end of usage to avoid memory leaks. The SystemVerilog garbage collector does not automatically deallocate CPython objects.

This example obtains a reference to the Python `Decimal` class.

    import pystim_pkg::*;

    // Equivalent to "from decimal import Decimal"
    py_object Decimal = py_module::import_("decimal").attr("Decimal").obtain();

    // Try to import scipy
    py_object scipy = py_module::import_("scipy");
    py_object version =  scipy.attr("__version__").obtain();

## Calling Python functions

It is also possible to call Python functions and methods with `attr()`.

    // Construct a Python object of class Decimal
    py_object pi = Decimal.call(py::float_(3.14159));

    // Use Python to make our directories
    py_object os = py_module::import_("os");
    py_object makedirs = os.attr("makedirs");
    makedirs.call(py::str_("/tmp/path/to/somewhere"));

## Calling Python methods

To call an object’s method, one can again use `.attr` to obtain access to the Python method.

    // Calculate e^π in decimal
    py::object exp_pi = pi.attr("exp").call();
    py::print(exp_pi));

In the example above `pi.attr("exp").call()` is a bound method: it will always call the method for that same instance of the class. Alternately one can create an unbound method via the Python class (instead of instance) and pass the `self` object explicitly, followed by other arguments.

    py::object decimal_exp = Decimal.attr("exp");

    // Compute the e^n for n=0..4
    for (int n = 0; n < 5; n++) {
        py_object d = Decimal.call(n);
        py::print(decimal_exp.call(d));
        d.delete();
    }
    decimal_exp.delete();

## Unpacking arguments

Unpacking of `*args` and `**kwargs` is also possible and can be mixed with other arguments:

    def f(number, message, obj):
        ...  # function code

    // * unpacking
    py_tuple args = py::tuple_({py::int_(1234), py::str_("hello"), some_instance});
    f.call(.args(args));

    // ** unpacking
    py_dict kwargs = py::dict("number"=1234, "message"="hello", "obj"=some_instance);
    f.call(.kwargs(kwargs));

    // mixed keywords, * and ** unpacking
    py_tuple args = py::make_tuple("hello");
    py_dict kwargs = py::dict("obj"=some_instance);
    f.call(args, kwargs);

Note

Generalized unpacking according to PEP448 not supported.

See also

The file `example/test_pytypes` contains a complete example that demonstrates passing native Python types in more detail. The file `example/test_callbacks` presents a few examples of calling Python functions from SystemVerilog, including keywords arguments and unpacking.

## Implicit casting

When using the SystemVerilog interface for Python types, or calling Python functions, objects of type `py_object` are returned. It is possible to invoke implicit conversions to subclasses like `dict`. The same holds for the proxy objects returned by `obj.call()` or `obj.attr()`. Casting to subtypes improves code readability and allows values to be passed to SystemVerilog functions that require a specific subtype rather than a generic `py_object`.

    #include <pybind11/numpy.h>
    using namespace pybind11::literals;

    py::module_ os = py::module_::import("os");
    py::module_ path = py::module_::import("os.path");  // like 'import os.path as path'
    py::module_ np = py::module_::import("numpy");  // like 'import numpy as np'

    py::str curdir_abs = path.attr("abspath")(path.attr("curdir"));
    py::print(py::str("Current directory: ") + curdir_abs);
    py::dict environ = os.attr("environ");
    py::print(environ["HOME"]);
    py::array_t<float> arr = np.attr("ones")(3, "dtype"_a="float32");
    py::print(py::repr(arr + py::int_(1)));

These implicit conversions are available for subclasses of `py_object`; there is no need to call `obj.cast()` explicitly as for custom classes, see Casting back and forth.

## Casting back and forth

Note

This functionality still not implemented in current version of PyStim.

Note

The following section is only relevant for custom classes. For built-in Python types, see Implicit casting.

In this kind of mixed code, it is often necessary to convert arbitrary SystemVerilog types to Python, which can be done using `py::cast()`:

    MyClass *cls = ...;
    py::object obj = py::cast(cls);

The reverse direction uses the following syntax:

    py::object obj = ...;
    MyClass *cls = obj.cast<MyClass *>();

When conversion fails, both directions throw the exception `cast_error`.

Note

If a trivial conversion via move constructor is not possible, both implicit and explicit casting (calling `obj.cast()`) will attempt a “rich” conversion. For instance, `py::list env = os.attr("environ");` will succeed and is equivalent to the Python code `env = list(os.environ)` that produces a list of the dict keys.

## Handling exceptions

Python exceptions from wrapper classes will be thrown as a `py::py_error`. See Handling exceptions from Python in SystemVerilog for more information on handling exceptions raised when calling C++ wrapper classes.