Python types
############

.. _wrappers:

Available wrappers
==================

All major Python types are available as thin SystemVerilog wrapper classes. These
can also be used as function parameters -- see :ref:`python_objects_as_args`.

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



.. _instantiating_compound_types:

Instantiating compound Python types from SystemVerilog
======================================================

Dictionaries can be initialized in the :class:`py_dict` methods using the
:func:`create` or :func:`empty` methods:

.. code-block:: systemverilog

    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 :func:`create` or :func:`empty` 
methods of :class:`py_tuple`:

.. code-block:: systemverilog

    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 :func:`create` or :func:`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 :func:`pystim_pkg::int_`, :func:`pystim_pkg::float_`,
:func:`pystim_pkg::str_`, :func:`pystim_pkg::bytes_`, :func:`pystim_pkg::list_`, 
:func:`pystim_pkg::tuple_`, :func:`pystim_pkg::dict_`, :func:`pystim_pkg::none_` and similar functions.

.. code-block:: systemverilog

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

The reverse direction uses the following syntax:

.. code-block:: systemverilog

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

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

.. _python_libs:

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 :func:`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.

.. code-block:: systemverilog

    import pystim_pkg::*;

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

.. code-block:: systemverilog

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


.. _calling_python_functions:

Calling Python functions
========================

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

.. code-block:: systemverilog

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


.. code-block:: systemverilog

    // 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:

Calling Python methods
========================

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

.. code-block:: systemverilog

    // 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.

.. code-block:: systemverilog

    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();


.. _unpack_args:
 
Unpacking arguments
===================

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


.. code-block:: python

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

.. code-block:: systemverilog

    // * 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.


.. seealso::

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

.. _PEP448: https://www.python.org/dev/peps/pep-0448/

.. _implicit_casting:

Implicit casting
================

When using the SystemVerilog interface for Python types, or calling Python functions,
objects of type :class:`py_object` are returned. It is possible to invoke implicit
conversions to subclasses like :class:`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 :class:`py_object`.

.. code-block:: systemverilog

    #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 :class:`py_object`; there
is no need to call ``obj.cast()`` explicitly as for custom classes, see
:ref:`casting_back_and_forth`.


.. _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 :ref:`implicit_casting`.

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

.. code-block:: cpp

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

The reverse direction uses the following syntax:

.. code-block:: cpp

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

When conversion fails, both directions throw the exception :class:`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.

..  TODO: Adapt text once PR #2349 has landed

Handling exceptions
===================

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

