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.