2024年7月

PDF文件中的图片可以丰富文档内容,提升用户的阅读体验。除了在PDF中添加图片外,有时也需要替换或删除其中的图片,以改进视觉效果或更新信息。本文将提供以下三个示例,介绍如何使用Python 操作PDF文件中的图片:

  1. Python 在PDF中添加图片
  2. Python 替换PDF中的图片
  3. Python 删除PDF中的图片

首先,我们需要安装一个名为
Spire.PDF for Python
的Python库,它可以用于处理PDF文件。可以使用pip来安装该库:

pip install Spire.PDF

示例1:使用Python在PDF中添加图片

Spire.PDF for Python库提供了
PdfPageBase.Canvas.DrawImage()
方法,可用于在PDF页面的指定坐标处绘制加载的图片。实现代码如下:

from spire.pdf.common import *
from spire.pdf import *
 
#创建PdfDocument对象
pdf =PdfDocument()#添加一页
page =pdf.Pages.Add()#加载一张图片
image = PdfImage.FromFile("图片1.jpeg")#指定图片尺寸
width = image.Width * 0.70height= image.Height * 0.70
 
#指定图片所在的XY坐标
x = 60.0y= 30.0
 
#将图片绘制在页面指定位置
page.Canvas.DrawImage(image, x, y, width, height)#保存PDF文件
pdf.SaveToFile("PDF添加图片.pdf", FileFormat.PDF)

生成文件:

示例2:使用Python替换PDF中的图片

要用新图片替换PDF中的图片,可以使用
PdfPageBase.ReplaceImage()

方法。实现代码如下:

from spire.pdf.common import *
from spire.pdf import *
 
#加载PDF文件
pdf =PdfDocument()
pdf.LoadFromFile(
"PDF添加图片.pdf")#获取第一页 page =pdf.Pages[0]#加载一张图片 image = PdfImage.FromFile("图片2.png")#获取页面中的图片信息 imageInfo =page.ImagesInfo#用加载的新图片替换页面中第一张图片 page.ReplaceImage(imageInfo[0].Index, image)#保存PDF文件 pdf.SaveToFile("替换图片.pdf", FileFormat.PDF)

效果图:

示例3: 使用Python删除PDF中的图片

要删除指定的图片可以使用
PdfPageBase.DeleteImage(index) 方法
。 实现代码如下:

from spire.pdf.common import *
from spire.pdf import *
 
#加载PDF文件
pdf =PdfDocument()
pdf.LoadFromFile(
"PDF添加图片.pdf")#获取第一页 page =pdf.Pages[0]#删除该页中的第一张图片 page.DeleteImage(0)#保存PDF文件 pdf.SaveToFile("删除图片.pdf", FileFormat.PDF)

Spire.PDF for Python库提供了相应的接口来添加、替换、删除PDF图片,通过运行上面3段示例代码即可实现对应的操作。如想了解更多Python处理PDF文档的示例,可查看:
https://www.e-iceblue.cn/pdfforpython/spire-pdf-for-python-program-guide-content.html

去除红色水印 --
点击申请试用授权

热点随笔:

·
周边上新,T恤上星:博客园T恤幸运闪系列,上架预售,上照预览
(
博客园团队
)
·
强烈推荐!!!阿里旗下10款顶级开源项目
(
程序员晓凡
)
·
给园子的会员送送优惠,和你的数据库聊聊天:会员权益「Chat2DB 特惠」上线
(
博客园团队
)
·
深入浅出分析最近火热的Mem0个性化AI记忆层
(
JadePeng
)
·
.NET跨平台UI框架Avalonia 11.1重磅发布
(
Setli
)
·
C# 网络编程:.NET 开发者的核心技能
(
小码编匠
)
·
记录荒废了三年的四年.net开发的第二次面试(进复试了)
(
ggtc
)
·
《HelloGitHub》第 100 期
(
削微寒
)
·
C# 开发技巧 轻松监控方法执行耗时
(
小码编匠
)
·
关于学习.NET的历程回顾与今后的探索实践方向
(
mingupupup
)
·
不是,大哥,咱这小门小户的,别搞我CDN流量啊
(
程序员老猫
)
·
由delete语句引起的锁范围扩大
(
桦仔
)

热点新闻:

·
视频平台现在不仅不让你投屏,还反过来蹭卡你家的网。。。
·
GitLab走上“卖身”路!前工程师拆台:赚钱的业务不好好运营,开发了一堆没用的功能
·
俞敏洪放手,董宇辉的职业抱负对东方甄选是福是祸?
·
龙芯 3C6000 服务器 CPU 流片成功
·
最新研究显示,睡眠时的大脑中仍有小部分区域保持清醒,反之亦然
·
Pura 70系列手机骤然降价,华为回应“价保政策差别对待”
·
两次全球蓝屏,祸首竟是同一人?14年后,灭霸CEO再酿IT灾难
·
从互联网大厂裸辞两年后,我决定重返职场
·
冰毒配方脱口而出,过去时态让GPT-4o防线崩塌!成功率从1%暴涨至88%
·
雷军讲故事,车圈学不会
·
我对互联网大厂离职员工进行了一次小范围调研
·
说好的拆中国设备就给补贴,结果美国电信说话不算话。

前言

Open CASCADE是由Open Cascade SAS公司开发和支持的开源软件开发平台,旨在为特定领域快速开发程序而设计。它是一个面向对象的C++类库,提供了丰富的几何造型、数据交换和可视化等功能,成为许多CAD软件的核心组件。
本篇描述搭建Qt开发occ环境过程。


Demo

注意:用的是内部QWidget提升为OccWidget,边框都是9px默认。
在这里插入图片描述


编译器版本问题

由于使用得occ的7.7,本身是msvc2015x64版本,所以Qt使用5.9.3的msvc2015x64版本(上一篇写成了msvc2017x64)。
在这里插入图片描述


搭建Qt开发环境


步骤一:新建demo,模块化思路

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述


步骤二:将occ的sdk模块化

这里本来打算将所有的三方库都放到一个bin、lib和include下混合,让pro里面要写xcopy脚本,后来发现不是很好,只好单独保持原样。
下面开始拷贝,将所有文件夹里面的bin里面的拷贝到模块的bin下,lib和include都做此操作,这里单个文件夹下都有bin、lib和include,只有occ没有。


draco-1.4.1-vc14-64:拷贝bin、include、lib

在这里插入图片描述


ffmpeg-3.3.4-64:拷贝bin、include、lib

在这里插入图片描述


freeimage-3.17.0-vc14-64:拷贝bin、include、lib

在这里插入图片描述


freetype-2.5.5-vc14-64:拷贝bin、include、lib

在这里插入图片描述


opencascade-7.7.0:拷贝inc和win64

inc中是头文件, win64中有bin和lib,保持相对原路径,已防止后续跟踪目录一致。
在这里插入图片描述

在这里插入图片描述


openvr-1.14.15-64:拷贝bin、include、lib

在这里插入图片描述


qt5.11.2-vc14-64:不拷贝,我们是Qt5.9.3 msvc2017x64(就是vc14-64)


rapidjson-1.1.0:拷贝include

这个比较特殊,源文件就在头文件里面,不是生成库。
在这里插入图片描述


tbb_2021.5-vc14-64:拷贝bin、include、lib

在这里插入图片描述


tcltk-86-64:拷贝bin、include、lib

在这里插入图片描述


vtk-6.1.0-vc14-64:拷贝bin、include、lib

在这里插入图片描述


步骤三:配置pri引入库

这里要引入头文件路径,库文件路径和库文件:
occManager.pri

INCLUDEPATH += $$PWD
DEPENDPATH += $$PWD

HEADERS += \
$$PWD/OccManager.h

SOURCES += \
$$PWD/OccManager.cpp

# occ-7.7.0-msvc2015x64(openCascade-7.7.0-vc14-64)
INCLUDEPATH += $$PWD/occ-7.7.0-msvc2015x64/opencascade-7.7.0/inc
LIBS += -L$$PWD/occ-7.7.0-msvc2015x64/opencascade-7.7.0/win64/vc14/lib
LIBS += -lTKBin
LIBS += -lTKBinL
LIBS += -lTKBinTObj
LIBS += -lTKBinXCAF
LIBS += -lTKBO
LIBS += -lTKBool
LIBS += -lTKBRep
LIBS += -lTKCAF
LIBS += -lTKCDF
LIBS += -lTKD3DHost
LIBS += -lTKD3DHostTest
LIBS += -lTKDCAF
LIBS += -lTKDFBrowser
LIBS += -lTKDraw
LIBS += -lTKernel
LIBS += -lTKExpress
LIBS += -lTKFeat
LIBS += -lTKFillet
LIBS += -lTKG2d
LIBS += -lTKG3d
LIBS += -lTKGeomAlgo
LIBS += -lTKGeomBase
LIBS += -lTKHLR
LIBS += -lTKIGES
LIBS += -lTKIVtk
LIBS += -lTKIVtkDraw
LIBS += -lTKLCAF
LIBS += -lTKMath
LIBS += -lTKMesh
LIBS += -lTKMeshVS
LIBS += -lTKMessageModel
LIBS += -lTKMessageView
LIBS += -lTKOffset
LIBS += -lTKOpenGl
LIBS += -lTKOpenGles
LIBS += -lTKOpenGlesTest
LIBS += -lTKOpenGlTest
LIBS += -lTKPrim
LIBS += -lTKQADraw
LIBS += -lTKRWMesh
LIBS += -lTKService
LIBS += -lTKShapeView
LIBS += -lTKShHealing
LIBS += -lTKStd
LIBS += -lTKStdL
LIBS += -lTKSTEP
LIBS += -lTKSTEP209
LIBS += -lTKSTEPAttr
LIBS += -lTKSTEPBase
LIBS += -lTKSTL
LIBS += -lTKTInspector
LIBS += -lTKTInspectorAPI
LIBS += -lTKTObj
LIBS += -lTKTObjDRAW
LIBS += -lTKToolsDraw
LIBS += -lTKTopAlgo
LIBS += -lTKTopTest
LIBS += -lTKTreeModel
LIBS += -lTKV3d
LIBS += -lTKVCAF
LIBS += -lTKView
LIBS += -lTKViewerTest
LIBS += -lTKVInspector
LIBS += -lTKVRML
LIBS += -lTKXCAF
LIBS += -lTKXDE
LIBS += -lTKXDECascade
LIBS += -lTKXDEDRAW
LIBS += -lTKXDEIGES
LIBS += -lTKXDESTEP
LIBS += -lTKXMesh
LIBS += -lTKXml
LIBS += -lTKXmlL
LIBS += -lTKXmlTObj
LIBS += -lTKXmlXCAF
LIBS += -lTKXSBase
LIBS += -lTKXSDRAW


步骤四:编译

出现常见错误:
在这里插入图片描述

查看文章《关于 fatal error LNK1158: 无法运行“rc.exe” 的解决方法》解决。(注意:直接赋值rc相关文件到qt的msvc2015_64版bin文件夹是最好的)。
在这里插入图片描述

至此,引入occ库是好了,但是我们还是要需要调用来检测。


步骤五:使用Demo进行编译运行检测

在这里插入图片描述
光包含opencascade的bin是不够的(跟我们预期一样,但是编译不把其它库lib带进去,就不知道了):
在这里插入图片描述

前面所有库的bin下面的dll都需要才可以正确运行起来,但是编译的时候确实不需要opencascade之外的lib了。
结果跑起来,程序没什么东西的情况下,发布部署包(Qt5.9.3 msvc2017x64 opencascade-7.7.0),有220MB左右,如下图:
在这里插入图片描述

一共303项:
在这里插入图片描述


Demo关键源码


OccWidget.h

protected slots:            // 显示出来之后需要刷一下大小让occ窗口显示大小一致
void slot_resize();

protected: // 需要重写父类的三个函数
QPaintEngine * paintEngine() const;
void paintEvent(QPaintEvent *event);
void resizeEvent(QResizeEvent *event);

private: // occ的专属变量
opencascade::handle<V3d_Viewer> _pV3dViewer;
opencascade::handle<V3d_View> _pV3dView;
opencascade::handle<Aspect_DisplayConnection> _pAspectDisplayConnection;
opencascade::handle<OpenGl_GraphicDriver> _pOpenGLGraphicDriver;
opencascade::handle<AIS_InteractiveContext> _pAisInteractiveContext;
WId _wid;
opencascade::handle<WNT_Window> _pWntWindow;
};


occWidget.cpp

void OccWidget::initControl()
{
// 初始化occ
{
// 显示到屏幕上
setAttribute(Qt::WA_PaintOnScreen);
// 创建连接显示设备
_pAspectDisplayConnection = new Aspect_DisplayConnection();
// 创建3D接口定义图形驱动
_pOpenGLGraphicDriver = new OpenGl_GraphicDriver(_pAspectDisplayConnection);
// 创建3D查看器对象,并指定图形驱动
_pV3dViewer = new V3d_Viewer(_pOpenGLGraphicDriver);
// 创建交互上下文对象,关联到3D查看器
_pAisInteractiveContext = new AIS_InteractiveContext(_pV3dViewer);
// 创建视图,并关联到3D查看器
_pV3dView = _pV3dViewer->CreateView();
// 获取窗口句柄并创建WNT_Window
_wid = winId();
_pWntWindow= new WNT_Window((Aspect_Handle)_wid);
// 设置视图窗口
_pV3dView->SetWindow(_pWntWindow);
if(!_pWntWindow->IsMapped())
{
_pWntWindow->Map();
_pV3dView->Redraw();
}
QTimer::singleShot(10, this, SLOT(slot_resize()));
}
}

void OccWidget::slot_resize()
{
_pV3dView->MustBeResized();
}

QPaintEngine *OccWidget::paintEngine() const
{
return 0;
}

void OccWidget::paintEvent(QPaintEvent *event)
{
if(!_pV3dView.IsNull())
{
_pV3dView->Redraw();
}
QWidget::paintEvent(event);
}

void OccWidget::resizeEvent(QResizeEvent *event)
{
if(!_pV3dView.IsNull())
{
_pV3dView->MustBeResized();
}
QWidget::resizeEvent(event);
}


入坑


入坑一:窗口拉伸出现重刷窗口问题


问题

拉伸的时候如下图:
在这里插入图片描述


原因

判断是刷了但是好像被覆盖了,后来找到原因是手码的时候代码问题
在这里插入图片描述


解决

在这里插入图片描述


入坑二:显示窗口第一次会出现重绘不对


问题

显示出现该问题
在这里插入图片描述

拉伸下则刷新没问题了:
在这里插入图片描述


原因

应该是刷新的问题,窗口没完全显示出来之前,是按照某个值(不定)刷的,这就跟动态布局在构造中获取大小不一致一样。


解决

这个只需要再构造中接一个槽函数(这个会确保启动后重新调用一次)。
在这里插入图片描述

在这里插入图片描述

自动化测试中,QTP和selenium IDE都支持浏览器录制与回放功能,简单的来说就像一个记录操作步骤的机器人,可以按照记录的步骤重新执行一遍,这就是脚本录制。
个人觉得传统录制工具有些弊端,加上要定制支持我自己的自动化框架(
python单机版自动化测试框架源代码
),所以自己用javascript写了一个录制工具,在控制台打印记录的python脚本如下:

代码如下(初稿,还在不断调试完善中):

var click_textContent = 'var father_level = 0;
var child_ctl = "";
var child_ctl_tmp = "";
var next_focusedElement = null;
window.clickedElement = null;

document.addEventListener("click", function(event) {
window.clickedElement = event.target;
console.log(window.clickedElement);
father_level = 0;
myDispose_click(window.clickedElement);
});

function myDispose_click(focusedElement) {
console.log(`开始 父${father_level} -------------------------`);
let tag_name = focusedElement.tagName.toLowerCase();
let outerHTML = focusedElement.outerHTML;
console.log(outerHTML);

if (tag_name === "body") {
let xpath = getElementfullXPath(next_focusedElement);
let elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (elements && elements.snapshotLength === 1) {
console.log(`self.myWtClickEx(driver, By.XPATH, "${xpath}")`);
console.log("结束:tag名称为body");
console.log(`结束 父${father_level} -------------------------`);
return xpath;
} else {
console.log("结束:tag名称为body");
console.log(`结束 父${father_level} -------------------------`);
return null;
}
}

let my_all_value = "";
let text = focusedElement.textContent.trim().replace(/"/g, "\\\"");
if (text !== "" && !text.includes("\\n")) {
my_all_value = `contains(text(),\'${text}\')`;
if (myDispose_count_number(text, "text", tag_name)) {
let xpath = `//${tag_name}[${my_all_value}]`;
let parameter = `driver, By.XPATH, "${xpath}"`;
myDispose_success(parameter);
return parameter;
}
} else {
text = ""
}

let attributes = focusedElement.attributes;
console.log(`属性名称列表: ${Array.from(attributes).map(attr => attr.name).join(",")}`);

for (let i = 0; i < attributes.length; i++) {
let attribute_name = attributes[i].name;
let attribute_value = attributes[i].value;

if (attribute_name === "class") {
let class_value_list = attribute_value.split(" ");
console.log(`class列表:${class_value_list}`);
if (class_value_list.includes("focusing")) {
class_value_list = class_value_list.filter(value => value !== "focusing");
}

for (let class_value of class_value_list) {
if (my_all_value === "") {
my_all_value = `contains(@class,"${class_value}")`;
} else {
my_all_value += ` and contains(@class,"${class_value}")`;
}

if (myDispose_count_number(class_value, attribute_name, tag_name)) {
let parameter = `driver, By.CLASS_NAME, "${class_value}"`;
myDispose_success(parameter);
return parameter;
}

let xpath = "";
if (text === "") {
xpath = `//${tag_name}[${my_all_value}]`;
} else {
xpath = `//${tag_name}[contains(text(),\'${text}\') and ${my_all_value}]`;
}
let result = myDispose_count_evaluate(xpath);
if (result) {
let parameter = `driver, By.XPATH, "${xpath}"`;
myDispose_success(parameter);
return parameter;
}
}
} else {
/*if (attribute_value === "" || /\d/.test(attribute_value)) {*/
if (attribute_value === "" || (attribute_name !== "src" && attribute_value.match(/[0-9]/))) {
continue;
}

if (my_all_value === "") {
my_all_value = `contains(@${attribute_name}, "${attribute_value}")`;
} else {
my_all_value += ` and contains(@${attribute_name}, "${attribute_value}")`;
}

if (myDispose_count_number(attribute_value, attribute_name, tag_name)) {
let xpath = `//${tag_name}[contains(@${attribute_name}, "${attribute_value}")]`;
let parameter = `driver, By.XPATH, "${xpath}"`;
myDispose_success(parameter);
return parameter;
}

let xpath = "";
if (text === "") {
xpath = `//${tag_name}[${my_all_value}]`;
} else {
xpath = `//${tag_name}[contains(text(),\'${text}\') and ${my_all_value}]`;
}
let result = myDispose_count_evaluate(xpath);
if (result) {
let parameter = `driver, By.XPATH, "${xpath}"`;
myDispose_success(parameter);
return parameter;
}
}
}

if (my_all_value !== "") {
let xpath = `//${tag_name}[${my_all_value}]`;
let result = myDispose_count_evaluate(xpath);
if (result) {
let parameter = `driver, By.XPATH, "${xpath}"`;
myDispose_success(parameter);
return parameter;
} else {
let textStr = `self.myWtClickEx(driver, By.XPATH, "${xpath}")`;
console.log("# 不是1");
console.log(textStr);
console.log(`结束 父${father_level} -------------------------`);

if (father_level === 0) {
child_ctl = `father, By.XPATH, ".${xpath}"`;
child_ctl_tmp = `.${xpath}`;
next_focusedElement = focusedElement;
}

let father = focusedElement.parentElement;
if (father) {
father_level++;
myDispose_click(father);
}
}
}
return null;
}

function myDispose_success(parameter) {
if (father_level === 0) {
console.log(`self.myWtClickEx(${parameter})`);
} else {
console.log(`father = self.myWtFindElement(${parameter})`);
console.log(`self.myWtClickEx(${child_ctl})`);
}
console.log(`结束 父${father_level} -------------------------`);
}

function myDispose_count_evaluate(xpath) {
let elements = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (father_level === 0) {
if (elements.snapshotLength === 1) {
return true
} else {
return null
}
} else {
if (elements.snapshotLength === 1) {
let firstElement = elements.snapshotItem(0);
let result = document.evaluate(child_ctl_tmp, firstElement, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
if (result.snapshotLength === 1) {
return true
} else {
return null
}
} else {
return null
}
}
}

function myDispose_count_number(attribute_value, attribute_name, tag_name) {
if (attribute_value === "") {
return null;
};
if (attribute_name !== "text" && attribute_name !== "src" && attribute_value.match(/[0-9]/)) {
return null;
};

let xpath;
if (attribute_name !== "text") {
xpath = `//${tag_name}[contains(@${attribute_name}, "${attribute_value}")]`;
} else {
xpath = `//${tag_name}[contains(text(), \'${attribute_value}\')]`;
};

let result = myDispose_count_evaluate(xpath);

if (result) {
console.log(`${attribute_name}:"${attribute_value}" 在网页中出现1次`);
return true;
} else {
console.log(`${attribute_name}:"${attribute_value}" 在网页中出现了多次`);
return null;
};
}

function getElementfullXPath(element) {
if (element && element.id)
if (!element.id.match(/[0-9]/)) {
return \'//*[@id="\' + element.id + \'"]\';
}

if (element==null)
return "";

var index = 0;
var loacl_tagName = element.tagName;
var sibling = element.previousSibling;
var sibling_tagName = null;
if (sibling) {
sibling_tagName = sibling.tagName;
}
while (sibling && sibling.nodeType === 1 && loacl_tagName === sibling_tagName) {
index++;
sibling = sibling.previousSibling;
if (sibling) {
sibling_tagName = sibling.tagName;
} else {
sibling_tagName = null;
}
}

parent = element.parentNode;
if (parent) {
var xpath = getElementfullXPath(parent);
if (xpath === "undefined") {
return "";
} else {
if (index === 0) {
xpath += "/" + element.tagName.toLowerCase();
} else {
xpath += "/" + element.tagName.toLowerCase() + "[" + (index+1) + "]";
}
return xpath;
}
} else {
return "";
}
}
';var input_textContent = 'let inputs = document.querySelectorAll(`input[type="text"]`);
inputs.forEach(input => {
input.addEventListener("input", function(event) {
console.log(this.value);
let parameter = myDispose_click(event.target);
if (parameter !== null) {
let textStr = `self.myWtSendKeysWebEx(${parameter}, "${this.value}")`;
console.log(textStr);
}
});
});
';/*iframe*/let iframes= document.getElementsByTagName('iframe');for (let i = 0; i < iframes.length; i++) {
let iframe
=iframes[i];if (iframe.contentWindow &&iframe.contentWindow.document) {
let script
= iframe.contentWindow.document.createElement('script');
script.type
= 'text/javascript';
script.textContent
=click_textContent;
iframe.contentWindow.document.head.appendChild(script);
let inputs
= iframe.contentWindow.document.querySelectorAll(`input[type="text"]`);
inputs.forEach(input
=>{
input.addEventListener(
"input", function(event) {
console.log(
this.value);
let parameter
=myDispose_click(event.target);if (parameter !== null) {
let textStr
= `self.myWtSendKeysWebEx(${parameter}, "${this.value}")`;
console.log(textStr);
}
});
});
}
}
/*非iframe*/let script= document.createElement('script');
script.type
= 'text/javascript';
script.textContent
= click_textContent +input_textContent;
document.head.appendChild(script);

技术背景

在前面的一篇
博客
中,我们介绍过使用VMD可视化H5MD标准化格式的轨迹文件的方法。H5MD本质上就是一个有
规范格式
的hdf5二进制文件,本文主要介绍两个关于hdf5的内容更新操作。

写入和更新数据

我们通常使用到的一个功能就是,通过
h5py.File
函数来打开或者创建一个hdf5文件,然后用
create_dataset
在文件中创建表单,再持续的向表单中填写数据。那么如果要更新文件中的数据怎么办呢?操作逻辑是比较简单的,直接加载对应的表单并获取返回值,然后直接在返回值中更新数据内容即可。如下是一个代码示例:

import h5py  
import numpy as np  
import os

h5_name = 'example.h5'
if os.path.exists(h5_name):
    with h5py.File('example.h5', 'r+') as file:  
        dataset = file['my_dataset']  
        new_data = np.random.rand(dataset.shape[0])
        dataset[...] = new_data

else:
    # 创建一个新的HDF5文件  
    with h5py.File(h5_name, 'w') as f:  
        dset = f.create_dataset("my_dataset", (10,), dtype='f')  
        data = np.arange(10)  
        dset[...] = data  

这个代码分成了两个部分,如果在指定的目录下不存在这个hdf5文件,我们就首先创建一个hdf5文件,表单内容为1~10的数字(这里使用了一个VSCode中的插件加H5Web来对h5文件进行可视化):

如果在路径下已经存在对应的h5文件,则修改其中的表单内容。例如我们把上述的测试代码执行两次,那么我们得到的h5文件内容是这样的:

刷新文件

hdf5文件作为一个规范格式的二进制文件,有严格的完整性校验。那么就会产生一个问题,如果在写入的过程中进程被中断,那么这个hdf5文件就会损坏:

当然,如果是
Ctrl+C
手动停止进程,那我们是可以参考这篇
博客
的内容进行终止信号的监听和管理的。但问题是如果被系统
kill -9
强行终止,是没办法捕获相关信号的。所以这里有一个方案,是通过flush,对中间过程进行保存,案例如下:

import h5py  
import numpy as np  
import time

h5_name = 'example.h5'
# 创建一个新的HDF5文件  
with h5py.File(h5_name, 'w') as f:  
    dset = f.create_dataset("my_dataset", (10,), dtype='f')  
    data = np.arange(10)  
    dset[...] = data 
    new_data = np.random.rand(dset.shape[0])
    f.flush()
    time.sleep(30)
    dset[...] = new_data 

这个案例中我们sleep了30秒的时间,在这个期间内我们会在系统中
kill -9
把这个Python进程杀死。如果没有加上
f.flush()
这一行,就会出现上面那张图中的报错,意味着这个hdf5文件是损坏的。如果加上了这一行代码,那效果如下:

这里需要说明的是,hdf5文件损坏只会出现在第一次写入hdf5文件的时候。如果使用第一个章节中的
r+
模式进行二次写入,不会有文件损坏的问题。

总结概要

hdf5是一个在量子化学和分子动力学模拟中经常有可能被用到的一种数据存储格式,得益于其良好的压缩率和完整性校验,一定程度上保障了其数据/轨迹存储的可靠性。本文介绍了关于hdf5文件的两个操作:更新已有的hdf5文件中的数据内容,以及flush同步更新的方法。

版权声明

本文首发链接为:
https://www.cnblogs.com/dechinphy/p/h5py-update.html

作者ID:DechinPhy

更多原著文章:
https://www.cnblogs.com/dechinphy/

请博主喝咖啡:
https://www.cnblogs.com/dechinphy/gallery/image/379634.html

参考文章

  1. https://arxiv.org/abs/1308.6382