我最近遇到一个这样的需求,即把某个软件中采集的数据按照特定的格式导出到world文档中。因为程序是用Qt开发的,所以想找一个满足要求的C++库,通过一番查询发现能完成这个需求的常用C++库有LibreOffice、OpenOffice。这两个库虽然能实现这一需求但是学习成本比较高,在规定的时间内完成这个需求比较困难。
这时只能将目光转向其他语言的读写word文档的库上,我发现python-docx 这个库不仅能实现需求而且学习成本很低,只要懂点Python几乎不需要额外花时间就能学会使用这个库。
本文主要从三个方面介绍了如何在Qt应用程序中调用Python实现数据导出到world文档中。这三个方面分别是C++调用python、在程序中使用Python虚拟环境及Python-docx库的基本应用。
麒麟V10 默认安装了python3.8,但是没有安装C++调用Python需的头文件,在系统中找不到Python.h文件。安装Python 开发环境,执行下面的命令:
sudo apt-get update
sudo apt-get install python3-dev
安装完成后会在/usr/include/python3.8 目录下看到需要的头文件。
新建Qt工程demo,并在pro文件中增加Python头文件和库的引用。
INCLUDEPATH += /usr/include/python3.8
LIBS += -L/usr/lib/python3.8/config-3.8-aarch64-linux-gnu -lpython3.8
//初始化Python C API
Py_Initialize();
//将Python文件所在的目录添加到环境变量,以便能正确加载Python文件。
PyRun_SimpleString("import sys");
QString pyfilePath = QString::fromLocal8Bit("sys.path.append('%1')").arg(qApp->applicationDirPath() + "/python");
qDebug() << "pyfilePath:" << pyfilePath;
PyRun_SimpleString(pyfilePath.toLocal8Bit().data());
QString printSysPath = "print(sys.path)";
PyRun_SimpleString(printSysPath.toLocal8Bit().data());
//加载Python文件,其中testdocx.py 所在的目录即为 qApp->applicationDirPath() + /python/testdocx.py
PyObject *pWriteWordModule = PyImport_ImportModule("testdocx");
if(!pWriteWordModule)
{
qDebug() << "import module faild!";
//如果失败打印错误,方便调试
PyErr_Print();
}
上面的代码,首先初始化Python环境,然后设置Python文件搜索路径到path中,最后加载testdocx.py 文件。
假设testdocx.py 文件中的内容如下,定义了一个函数,并打印传进来的参数。
def writeWord(wordPath):
print(wordPath)
在C++中调用这个函数
//获取Python中定义的函数的指针
PyObject *pFunc = PyObject_GetAttrString(pWriteWordModule, "writeWord");
if(!pFunc)
{
qDebug() << "get func faild!";
}
//初始化参数元组,其中包含一个参数
PyObject *pTuple = PyTuple_New(1);
QString wordFilePath = QString("%1/python/%2").arg(qApp->applicationDirPath()).arg(QString::fromLocal8Bit("xxx.docx"));
//设置参数
PyTuple_SetItem(pTuple, 0, Py_BuildValue("s", wordFilePath.toLocal8Bit().data()));
//调用Python中的函数
PyObject_CallObject(pFunc, pTuple);
运行程序,如果一切顺利的话会在终端打印:
/home/demo/bin/python/xxx.docx
PyObject 在C++和Python混合编程中最常用的一个类型,所有对象类型都是这个类型的扩展。PyObject中包含了Python需要将对象的指针视为对象的信息。在一般的“release”版本中,它只包含对象的引用计数和指向相应类型对象的指针。实际上并没有任何东西被声明为PyObject,但是每个指向Python对象的指针都可以转换为PyObject*。必须使用宏Py_REFCNT和Py_TYPE来访问成员。下面是一些编程中常用的Python C API。
#include
int main(int argc, char* argv[]) {
Py_Initialize();
int x = 42;
double y = 3.14;
char* z = "Hello, World!";
PyObject* result = Py_BuildValue("(iO)", x, Py_None);
if (result == NULL) {
PyErr_Print();
return 1;
}
printf("Result: %s\n", PyUnicode_AsUTF8(result));
Py_DECREF(result);
Py_Finalize();
return 0;
}
//函数原型
PyAPI_FUNC(int) PyDict_SetItemString(PyObject *dp, const char *key, PyObject *item);
//示例:
PyObject* pDictObj = PyDict_New();
PyObject* deviceNameObj = Py_BuildValue("s", deviceData.deviceName.toLocal8Bit().data());
PyDict_SetItemString(pDictObj, "deviceName", deviceNameObj);
//函数原型
PyAPI_FUNC(int) PyList_Append(PyObject *, PyObject *);
//示例:
PyObject *PointDataListObj = PyList_New(0);
PyList_Append(PointDataListObj, pDictObj);
//函数原型
int PyRun_SimpleString(const char *command);
//示例
const char* code = "print('Hello, World!')";
int result = PyRun_SimpleString(code);
if (result == 0) {
//执行成功
printf("Successfully executed code.\n");
} else {
//执行失败
printf("Failed to execute code.\n");
}
//函数原型
PyObject* PyImport_ImportModule(const char *name);
//说明,该函数返回一个Python对象,表示导入的模块。如果导入成功,则返回该模块对象的引用,否则返回NULL。
//示例:
PyObject* mymodule = PyImport_ImportModule("mymodule");
if (mymodule != NULL) {
printf("Successfully imported mymodule.\n");
} else {
printf("Failed to import mymodule.\n");
}
//函数原型
PyObject* PyObject_CallObject(PyObject *callable, PyObject *args);
//说明,callable是一个指向要调用的Python对象(如函数或方法)的指针,args是一个指向参数列表的指针。
//该函数返回调用结果,表示为Python对象。如果调用成功,则返回调用结果的引用,否则返回NULL。
//示例:
PyObject* mymodule = PyImport_ImportModule("mymodule");
PyObject* myfunction = PyObject_GetAttrString(mymodule, "myfunction");
PyObject* args = PyTuple_New(1);
PyTuple_SetItem(args, 0, PyLong_FromLong(42));
PyObject* result = PyObject_CallObject(myfunction, args);
if (result != NULL) {
printf("Function returned: %ld\n", PyLong_AsLong(result));
} else {
printf("Failed to call function.\n");
}
virtualenv和anaconda都是用于创建Python虚拟环境的工具,二者的主要区别如下:
在这里选择virtualenv,原因是anaconda 在飞腾架构的麒麟系统上安装失败。
sudo apt-get install pip
pip install virtualenv
cd /home/demo/bin/python
virtualenv demoEnv
#或者指定Python版本
virtualenv -p /usr/bin/python3.8 demoEnv
demoEnv\Scripts\activate
pip install -r requirements.txt
pip install python-docx
demoEnv\Scripts\deactivate
demo程序利用了系统自带的Python3.8环境,所以在C++程序中只需配置好虚拟环境的包安装目录即可,这点是参考在终端通过命令执行Python脚本时的环境设置。例如执行下面的命令来查看当前Python环境的path内容:
python3 test.py
#其中 test.py 内容如下
import sys
print(sys.path)
因此Qt程序中Python环境初始化部分要增加设置Python虚拟环境安装包路径的代码,如下:
QString modulePath = QString::fromLocal8Bit("sys.path.append('%1')").arg(qApp->applicationDirPath() + "/python/demoEnv/lib/python3.8/site-packages");
PyRun_SimpleString(modulePath.toLocal8Bit().data());
经过上述设置就可以在虚拟环境中用pip命令安装应用程序所需的python包,并在应用程序所在目录的python文件中使用第三方包,如python-docx。当Qt程序需要发布到其他飞腾架构的麒麟V10设备上时,可以直接把虚拟环境一起打包,省去了在新的设备安装依赖包的步骤。
官方参考文档https://python-docx.readthedocs.io
python-docx 作为word文档读写库算是很轻量了,其涉及的概念不多,很容易理解与掌握。下面是python-docx的一些核心概念。
下面是一个快速上手指南:
from docx import Document
document = Document()
paragraph = document.add_paragraph('Lorem ipsum dolor sit amet.')
或
prior_paragraph = paragraph.insert_paragraph_before('Lorem ipsum')
document.add_heading('The REAL meaning of the universe')
或
document.add_heading('The role of dolphins', level=2)
document.add_page_break()
table = document.add_table(rows=2, cols=2)
cell = table.cell(0, 1)
cell.text = 'parrot, possibly dead'
row = table.rows[1]
row.cells[0].text = 'Foo bar to you.'
row.cells[1].text = 'And a hearty foo bar to you too sir!'
for row in table.rows:
for cell in row.cells:
print(cell.text)
row_count = len(table.rows)
col_count = len(table.columns)
row = table.add_row()
# get table data -------------
items = (
(7, '1024', 'Plush kittens'),
(3, '2042', 'Furbees'),
(1, '1288', 'French Poodle Collars, Deluxe'),
)
# add table ------------------
table = document.add_table(1, 3)
# populate header row --------
heading_cells = table.rows[0].cells
heading_cells[0].text = 'Qty'
heading_cells[1].text = 'SKU'
heading_cells[2].text = 'Description'
# add a data row for each item
for item in items:
cells = table.add_row().cells
cells[0].text = str(item.qty)
cells[1].text = item.sku
cells[2].text = item.desc
table.style = 'LightShading-Accent1'
document.add_picture('image-filename.png')
#设置图片尺寸
from docx.shared import Inches
document.add_picture('image-filename.png', width=Inches(1.0))
以上就是本篇的所有内容了,关于python-docx应用的一些细节本文并未涉及太多,想要了解更多的话还是要阅读官方文档。对C++ python混合编程有疑问的朋友欢迎留言讨论!!!