无法让 matplotlib 事件处理程序与 Boost.Python 一起使用
Posted
技术标签:
【中文标题】无法让 matplotlib 事件处理程序与 Boost.Python 一起使用【英文标题】:Can't get matplotlib event handler working with Boost.Python 【发布时间】:2019-01-04 22:32:56 【问题描述】:我正在开发一个带有解释器(用 C++ 编写)的 C++11 程序,它调用各种类方法来完成工作。我想添加使用 Python 3.5 和 matplotlib(后端是 TkAgg)创建数据图的能力。我还希望用户能够在程序中启动 Python 解释器或运行 Python 脚本,以便实时对绘图进行详细调整/增强。
到目前为止,我已经成功地使用 Boost.Python 1.65.1 作为我的接口层创建和保存了绘图,并使用 Python 的代码模块启动了 Python 解释器。但是,matplotlib 图的事件循环不会在我的 C++ 解释器提示符下运行(即,如果我在我的图上放置另一个窗口,该图将变为空白并且不会重绘)。只有当用户启动 Python 解释器时,事件循环才会起作用并重新绘制图形。我希望程序的行为就像用户在发出 matplotlib.pyplot.ion() 命令后在本机 Python 解释器中创建图一样。我在用于创建绘图的 Python 代码中添加了 plt.ion() 调用,但这似乎对结果行为没有影响。
我试图通过在不同线程上执行 Python 绘图和 C++ 解释来解决此问题,但它似乎没有任何帮助。我想知道我是否应该以不同于我目前的方式来处理 Python GIL,或者我是否可以做其他事情来让 matplotlib 事件循环在后台线程中运行。我发布了一个简单的示例,重现了我遇到的核心问题。任何帮助表示赞赏。
我尝试过的另一件事是手动调用 matplotlib 的 canvas.start_event_loop()。这似乎可行,但它阻止我创建新的情节,直到它返回/超时,这并不理想。
这是创建绘图的 Python 代码
# Import matplotlib and pyplot and turn on interactive mode
import matplotlib
import matplotlib.pyplot as plt
plt.ion()
def PlotData(num):
""" Create a simple plot and return the figure.
"""
data_list = [[1,2,3,4],[3,2,4,1],[4,3,2,1]]
data = data_list[num]
f = plt.figure()
ax = f.gca()
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.plot([1,2,3,4],data,'bo-')
title = 'Data Set ' + str(num+1)
f.suptitle(title)
print(title)
f.show()
return f
这是 C++ 代码
#include <boost/python.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/chrono.hpp>
#include <iostream>
#include <string>
PyThreadState* mainThreadState = nullptr;
PyThreadState* pts = nullptr; /*!< Pointer for the current thread state */
/*! Initialize Python */
void init()
Py_Initialize();
PyEval_InitThreads();
mainThreadState = PyEval_SaveThread();
void init_thread()
pts = PyThreadState_New(mainThreadState->interp);
/*! Calls Python to create a simple plot interactively */
void PlotData(const int& inp)
using namespace boost::python;
// Acquire the GIL
std::cout << "Python Thread GIL State (before): " << PyGILState_Check() << std::endl;
PyEval_RestoreThread(pts);
object background = import("sto");
object fig = background.attr("PlotData")(inp);
// The code below will show the plot, but the plot won't update in real time at the C++ command line
object plt = import("matplotlib.pyplot");
fig.attr("show")();
plt.attr("pause")(.1);
std::cout << "Python Thread GIL State (during): " << PyGILState_Check() << std::endl;
// Release the GIL
pts = PyEval_SaveThread();
std::cout << "Python Thread GIL State (after): " << PyGILState_Check() << std::endl;
int main(int argc, char* argv[])
// Create a thread pool with 1 thread for all Python code
boost::asio::io_service ioService;
boost::thread_group threadpool;
// Start the service
auto work = std::make_shared<boost::asio::io_service::work>(ioService);
// Add a single thread to the thread pool for Python operations
threadpool.create_thread(boost::bind(&boost::asio::io_service::run, &ioService));
// Initialize Python
init();
std::cout << "Submitting init_thread" << std::endl;
ioService.post(boost::bind(init_thread));
// Create some plots on the background thread
std::cout << "Submitting PlotData calls" << std::endl;
ioService.post(boost::bind(PlotData,0));
ioService.post(boost::bind(PlotData,1));
// Pause to allow plots to update in realtime
boost::this_thread::sleep_for(boost::chrono::seconds(4));
// Receive inputs from command line (the plots should update during this time but they don't)
std::string inp"1";
std::cout << "Enter to quit\n";
while (!inp.empty())
std::cout << ">> ";
std::getline(std::cin,inp);
std::cout << "GIL State: " << PyGILState_Check() << std::endl;
// Finalize Python
std::cout << "Submitting Py_Finalize" << std::endl;
ioService.post(boost::bind(PyEval_RestoreThread,pts));
ioService.post(boost::bind(Py_Finalize));
// Allow jobs to complete and shut down the thread pool
work.reset();
threadpool.join_all();
ioService.stop();
return 0;
【问题讨论】:
【参考方案1】:如果有人偶然发现这篇文章并感兴趣,我找到了一个合适的前进方向。我放弃了 Boost.Asio 方法,转而使用 std::future 更直接的方法。此外,我更新了代码以在后台线程上获取输入,而不是在后台运行 Python。我不得不玩弄超时时间来获得我想要的响应能力,但是现在 Python 图形更新而没有成为焦点,用户可以在 C++ 提示符下输入命令。下面的代码假设当 C++ 提示符启动时,一个图形已经在 Python 中打开。
这是更新后的 C++ 代码:
#include <iostream>
#include <string>
#include <future>
#include <chrono>
#include <Python.h>
#include <boost/python.hpp>
void GetInput()
std::cout << "$ ";
std::string tline;
std::getline(std::cin, tline);
std::cout << "You entered: '" << tline << "'\n";
int main(int argc, char* argv[])
using namespace boost::python;
Py_Initialize();
try
std::cout << "Importing python code" << std::endl;
object background = import("sto");
std::cout << "Calling python code" << std::endl;
background.attr("SimpleTest")(argc);
object fig = background.attr("PlotData")(argc);
std::chrono::milliseconds span(100);
for (int i = 0; i < 10; ++i)
std::cout << "Iteration " << i << std::endl;
std::future<void> fut = std::async(GetInput);
while (fut.wait_for(span) == std::future_status::timeout)
background.attr("Pause")(.2);
catch (error_already_set& e)
PyErr_Print();
Py_Finalize();
return 0;
以及导入的相应 Python 代码(来自 sto.py):
# Import matplotlib and pyplot and turn on interactive mode
import sys
import matplotlib
import matplotlib.pyplot as plt
plt.ion()
plt.style.use('classic')
def SimpleTest(num):
print('Python code: you entered ' + str(num))
def PlotData(num):
""" Create a simple plot and return the figure.
"""
if len(sys.argv) == 0:
sys.argv.append('sto.py')
data_list = [[1,2,3,4],[3,2,4,1],[4,3,2,1]]
if num < len(data_list):
data = data_list[num]
else:
data = data_list[1]
f = plt.figure()
ax = f.gca()
ax.set_xlabel('x label')
ax.set_ylabel('y label')
ax.plot([1,2,3,4],data,'bo-')
title = 'Data Set ' + str(num+1)
f.suptitle(title)
print(title)
f.show()
plt.pause(.02)
return f
if __name__ == '__main__':
SimpleTest(8)
PlotData(4)
def Pause1(timeout):
'''This function isn't great because it brings the
figure into focus which is not desired'''
plt.pause(timeout)
def Pause2(timeout):
'''This function starts the event loop but does not
bring the figure into focus (which is good)'''
fig = plt.gcf()
fig.canvas.start_event_loop(timeout)
def Pause(timeout):
# print('Waiting for ' + str(timeout) + ' seconds')
Pause2(timeout)
# print('Done waiting')
【讨论】:
以上是关于无法让 matplotlib 事件处理程序与 Boost.Python 一起使用的主要内容,如果未能解决你的问题,请参考以下文章
Matplotlib 事件处理:他们何时发送 matplotlib 事件以及何时发送 Qt 事件
PyQT5 与 matplotlib 图,事件循环已经在运行
是否必须以不同的方式处理键盘事件才能在 Matplotlib/PyQt5 中选择事件? [复制]