Summary

The initial task

One of our clients asked us to unlock a Python module to their MATLAB tool-suite with which they do their analyses. The module would be part of a Conda Python environment, use matplotlib to visualize results, and would be used from MATLAB R2019a and R2021a. The tool-suite's startup functions should ensure the correct Python environment is started and that the necessary resources are found.

We have achieved this, but it proved to be more work than we had originally anticipated. In the following sections we will describe some of the steps we took, the difficulties we ran into, and the solutions we used to overcome them.

Selecting a Python interpreter

Python functionality can be called from MATLAB by prepending the Python call with a py. prefix. The Python interpreter that is used to execute the call, can be set with MATLAB's pyenv function (R2019b and later). In MATLAB R2019a and before you can use the pyversion function. Please refer to the official documentation for an introduction to these functions.

The pyenv function has an optional name-value pair input ExecutionMode that accepts the following options:

InProcess {default}

runs Python in the same process as MATLAB. Offers the best performance.

It is not possible to change the Python interpreter after MATLAB loads a Python environment InProcess. To use another Python interpreter you need to restart MATLAB.

OutOfProcess

runs Python in a separate process, which allows using resources that conflict with MATLAB resources, and prevents MATLAB crashing if the Python process crashes.

Note that changes to the MATLAB environment made with setenv are included in the OutOfProcess Python environment, if these changes were made before Python is started.

The OutOfProcess Python environment can be terminated with the terminate function and another one can be started OutOfProcess without restarting MATLAB. If your OutOfProcess environment was terminated and you make a py. call, a new process is started from the same Python version.

Note that both ExecutionModes allow for debugging and for reloading modified Python code without restarting MATLAB.

Setting up Python with a function

When your MATLAB - Python code is to be used by others, you may want to include a utility function to ease setting up and loading the Python environment in MATLAB. Note that you should not use pyenv (or pyversion) and make a py. call in the same MATLAB function. The MATLAB interpreter will parse the py. statement and load the default Python environment before executing the pyenv function call. When the pyenv is executed to load the intended Python environment, MATLAB returns an error as pyenv cannot replace the Python environment that was loaded on parsing the py. statement.

So, the following function will result in an error when called:

    function startup()
        pyenv("version", "C:\Programs\Python\3.8.10\python.exe");
        aList = py.list({1, 2});
    end
Error using pyenv
Python is loaded. The environment cannot be changed in this MATLAB session. To change the environment, restart MATLAB, and then call > 'pyenv'.

You can prevent this error by putting the py. statement in another (sub)function:

    function startup()
        pyenv("version", "C:\Programs\Python\3.8.10\python.exe");
        aList = createList();
    end

    function aList = createList()
        aList = py.list({1, 2});
    end

Note that this error also does not occur when the createList function is called from a script file instead of a function or when the commands are executed in the Command Window.

When you are loading Python OutOfProcess, the error message is different and the Python environment can be changed by using the terminate function.

Error using pyenv
Python is loaded. The environment cannot be changed while the interpreter is running. To change the environment, call 'terminate(pyenv)> ' then call 'pyenv'.

terminate(pyenv)

The terminate function of MATLAB's PythonEnvironment class allows for terminating Python processes that were started OutOfProcess. The Python environment is restarted when you make another py. call.

InProcess, Loaded Python environments cannot be terminated. MATLAB needs to be restarted to select another version.

Error using matlab.pyclient.PythonEnvironment/terminate
Termination not supported for InProcess execution mode.

Note: It is not possible to start an InProcess Python environment when you have used (and terminated) an OutOfProcess Python environment. It is possible to start another Python version OutOfProcess.

Error using pyenv
Python loaded in 'OutOfProcess' mode. To change the 'ExecutionMode' to 'InProcess', restart MATLAB, and then call 'pyenv'.

Fix Python's sys modules attributes

Some Python modules use sys module attributes containing the path to the Python executable (or its parent folder) to find resources in the Python installation. These paths are different when Python is called from MATLAB, which may cause these modules to error. The fix for this issue is to correct these paths after the Python interpreter is started, as described on MATLAB central.

    def fix_sys_attrs(prefix: str, exec_path: str) -> None:
        """Fix sys attributes that were not correctly set when MATLAB started the Python Interface.

        Args:
            prefix:         Path to the Python installation.
            exec_path:      Path to the Python executable.
        """
        sys.prefix = prefix
        sys.exec_prefix = prefix

        sys.executable = exec_path

Unable to resolve the name py.

At some point you may call one of your own Python functions from MATLAB similar to:

    function test()
        insert(py.sys.path, int32(0), path_to_test_module);
        py.test_module.test_function();
    end

and get an error:

Unable to resolve the name py.test_module.test_function.

The MATLAB documentation contains a help page with potential causes and solutions to a problem like this. If none of these fix your problem and you are able to load the module into MATLAB and execute the function via the module object:

    mod = py.importlib.import_module('test_module');
    mod.test_function();

then the problem may be caused by MATLAB having cached the function as 'unresolvable'. This may be caused by calling functionality of the module in the same MATLAB function file where the Python module is put on the MATLAB and Python path. This results in the MATLAB interpreter looking for the Python module before it can be found.

As a quick workaround the following command should result in the function being directly callable again:

    clear classes

(Note that running this command also clears all variables in the current scope.)

Using a Python Virtual Environment

Using a Python virtual environment was not supported in the past. It is supported since MATLAB R2022b and R2022a - Update 4: (MATLAB bug report)

The workaround for using virtual environments in earlier MATLAB versions is to temporarily add the folder containing the Python executable to your system's Path.

    pyenv("version", "C:\.venv\test\Scripts\python.exe");
    extraPaths = "C:\.venv\test\Scripts\";
    setenv("path", strjoin(extraPaths, ";") + ";" + getenv("path"));

Using a Conda environment.

Using a Python environment created with Conda is not supported. By adding folders of the environment to your system path as shown here, it is possible to let MATLAB work with a Conda Python environment. (Note that the example is for Windows. For Unix refer to the Python activate script to see which folders it puts on the path.)

    pyenv("version", "C:\Users\me\.conda\envs\test\python.exe");
    extraPaths = [
        "C:\Users\me\.conda\envs\test"
        "C:\Users\me\.conda\envs\test\Library\mingw-w64\bin"
        "C:\Users\me\.conda\envs\test\Library\usr\bin"
        "C:\Users\me\.conda\envs\test\Library\bin"
        "C:\Users\me\.conda\envs\test\Scripts"
        "C:\Users\me\.conda\envs\test\bin"
        ];
    setenv("path", strjoin(extraPaths, ";") + ";" + getenv("path"));

Creating matplotlib figures

The matplotlib module is useful for presenting results graphically. matplotlib figures can be created and shown from MATLAB by using the py. syntax. There are a couple of things to consider, which are described in more detail in the following sections:

Ensure Tcl library can be found

To create figures with matplotlib, the Tcl (and optionally Tk) library needs to be made available to Python as TCL_LIBRARY (and TK_LIBRARY) environment variables. This can be done via MATLAB's setenv() function. Note that for OutOfProcess mode these variables should be set before the Python process is started, or be set directly in the os.environ dictionary through a Python function.

Using another backend

The backend that is used by matplotlib by default may be suitable for exporting the figures to a file type, but not for showing the figures. The 'agg' backend for instance is meant for exporting to .png files and triggers the following error message when trying to show a figure:

UserWarning: Matplotlib is currently using agg, which is a non-GUI backend, so cannot show the figure.

You can check which backend is used by matplotlib by running the following command:

    py.matplotlib.get_backend()

The Tk library can be used as matplotlib backend to show figures on a Tk canvas, as suggested in this MATLAB Answer. Depending on what is available in your Python installation, you may want to use another backend supported by matplotlib. To make matplotlib use the TkAgg backend run:

    py.matplotlib.use('TkAgg');

Note that the Tk library will need to be added to the environment variable TK_LIBRARY, similar to what is described in the previous section for the Tcl library.

Showing figures

The code snippet below creates (but does not show) two simple matplotlib figure objects.

    py.matplotlib.pyplot.plot([1, 2, 3, 4], [1, 2, 3, 4]);
    f = py.matplotlib.pyplot.figure();
    py.matplotlib.pyplot.plot([1, 2, 3, 4], [4, 3, 2, 1]);

These matplotlib figures can be shown from MATLAB, but only if the MATLAB process is blocked when the figure windows are created. If the MATLAB process is not blocked, the figures are not shown correctly and MATLAB (or the OutOfProcess Python process) crashes when the figure window is resized or closed.

The following matplotlib function triggers all created figures to be shown in separate windows. The MATLAB process will be blocked until all matplotlib figure windows are closed.

    py.matplotlib.pyplot.show()

The same function can be called with an input argument to not block the MATLAB process, but this results in empty figure windows and MATLAB crashes when the windows are resized or closed.

    py.matplotlib.pyplot.show(pyargs('block', false));

In OutOfProcess mode it is also not possible to show matplotlib figures without blocking the MATLAB process.

The show method of a matplotlib figure object will try to show the figure without blocking the MATLAB process, causing the figure to not be created correctly and MATLAB to potentially crash. The function does not have an optional input argument with which the MATLAB process can be blocked, so you need to use the matplotlib.pyplot.show() function to be able to show matplotlib figures from MATLAB.