简介:本资源是面向Delphi开发者(尤其适配Delphi 12.3)的Python集成开发套件,解决原生Delphi难以直接调用Python脚本、扩展模块及双向交互的技术瓶颈。它基于开源项目Python for Delphi(P4D),提供从底层Python API封装到高级Delphi自定义Variant(VarPyth.pas)的全栈支持,适用于嵌入式Python引擎、开发Python扩展DLL、构建混合型桌面应用等典型场景。压缩包共499个文件,含137个核心Pas单元(如TPythonEngine、TPythonVariant等组件实现)、53个Delphi工程文件(dproj/dpr)、41个窗体设计文件(dfm)、26个Python示例脚本及配套资源文件,整体体积7.62MB,结构完整、开箱即用。目前已有80人学习下载,资源包含BuildAllVersions.bat等自动化构建脚本、单元测试工程(VarPythUnitTest.bdsproj)及多图标/位图资源(如TPYTHONENGINE.bmp),便于快速验证、二次开发与深度集成。 Delphi 12.3 + python4delphi-master.zip,这组合乍一看像老古董碰上新玩具,但如果你手里恰好维护着一个跑了很多年的 Delphi 业务系统,又不想因为“加个AI能力”“加个数据分析功能”就把整个项目推倒重写,那你真的应该停下来看看这个 zip 包。
Python4Delphi(简称 P4D)是一个把 CPython 解释器直接嵌进 Delphi 程序的开源控件集。简单说,你在 Delphi 里拖一个 TPythonEngine 控件,就能在自己的 Windows 程序里执行 Python 脚本、调用 Python 函数,反过来 Python 也能回调 Delphi 里的方法。解压后就是 python4delphi-master 这个目录,里面有源码、Demo、文档,以及按 Delphi 版本分好的安装包。这篇文章我打算从“为什么值得装”开始,把编译安装、核心组件的运行机制、跨语言的数据交换套路,再到实际项目中一定会碰到的版本兼容、DLL 缺失、打包分发、多线程问题,一次讲清楚。
1. 先说清价值:这个zip包帮Delphi项目补上了什么短板
1.1 不是“调用外部程序”,是把Python请进进程里
很多人一想到 Delphi 用 Python,第一反应是“我用 ShellExecute 调 python.exe 不就行了?”这个思路能做,但差别很大。这种方式每次执行都要启动一个 Python 进程,参数传递基本靠命令行和文件,返回值要读输出流,一旦脚本 import 的包比较多,光启动解释器就是几百毫秒到一两秒的开销,而且进程之间的数据交换非常别扭。
P4D 走的是另一条路:它把 python312.dll 作为动态库加载到 Delphi 进程内,解释器和你程序的窗体、对象共享同一块内存空间。这意味着你可以直接执行PythonEngine1.ExecString('print("hello")'),也能通过PythonEngine1.SetVarValue('x', 42)把一个 Delphi 变量推进 Python 的命名空间里。没有进程启动,没有命令行解析,也没有文本输出流的编码坑,数据和调用都发生在同一个进程内部。
这个本质区别决定了适用场景。如果只是偶尔跑一个独立的 Python 工具脚本,走外部进程没问题;但如果你的业务逻辑需要频繁调用 Python、需要共享状态、需要用 Python 包处理数据再回传给 Delphi,嵌入式的优势就是碾压级的。
1.2 老系统的“第二语言”到底能干什么
我实际用下来的感受是,P4D 最值钱的地方不是让你“在 Delphi 里写 Python”,而是让老 Delphi 项目能以极低成本接入 Python 生态。典型的例子:
- 业务规则热更新:主程序编译一次,规则脚本全部外置成 .py 文件,客户改规则不用重新发版。
- 数据处理:Delphi 做界面和业务外壳,Python 用 pandas/openpyxl 处理 Excel、清洗数据,跨语言的数据用 JSON 串交换。
- 算法扩展:需要调 numpy、scipy、sklearn 甚至加载训练好的模型时,Delphi 本身没有现成方案,Python 一抓一大把。
- 解决 Delphi 生态里“有但很麻烦”的功能:比如 MD5、正则、批量文件处理、执行系统命令并拿回输出,这些在 Python 里都是标准库几行的事。
但也不是所有项目都适合装。P4D 本质上是 Windows 桌面端的方案(第三方改造移动端另说),如果目标是跨平台发布,或者要在极高性能要求的循环体里做逐行调用,P4D 就不太合适。语言边界的穿越开销再小也是有成本的,适合“批量处理”而不是“高频逐条调用”。
2. 从压缩包到IDE组件栏:Python4Delphi完整装起来
2.1 环境准备:Delphi 12.3和Python版本的对应关系
在动手编译之前,先把 Python 装好。这里有个容易犯迷糊的点:P4D 不是“自带 Python 解释器”,它只是承载解释器的外壳,真正的解释器是 CPython 的 DLL。所以你必须先有一个可用的 Python 环境,然后把 DLL 路径、版本告诉 P4D。
我当时用的是 Python 3.12,64 位,安装目录放在了D:\Python312。之所以强调 64 位,是因为 Delphi 12.3 的 IDE 本身就是 64 位程序,设计期包必须编译成 Win64 才能装进去;而如果你的目标程序要跑 32 位,运行时包还得单独编译一份 Win32 的,并且匹配 32 位的 python312.dll。
安装路径建议选一个没有空格的简单目录,比如D:\Python312,不要装在C:\Program Files\Python312下。这不是 P4D 的强制要求,但后面配置 DllPath 时少很多麻烦,尤其当你需要把 Python 运行时连同程序一起打包分发时,路径越简单越省心。
版本选择上,我当时避开了最新的 Python 3.13,选 3.12 是因为 master 分支的 P4D 对 3.12 的支持已经非常成熟,Demos 里也有完善的示例。如果你用的是其他版本,关键是保证DllName属性(默认填python312.dll)和你安装的 Python 主版本一致。
2.2 打开工程,按正确顺序编译
解压 python4delphi-master.zip 后,目录结构大致是这样的:
python4delphi-master/ Packages/ # 按 Delphi 版本分的 dpk 包 Source/ # 核心源码 Demos/ # 官方示例,强烈推荐通读 Docs/ # 文档 Python4Delphi.groupproj用 Delphi 12.3 打开根目录的Python4Delphi.groupproj,你会看到项目组里同时包含运行时包(RT)和设计时包(DT)两组项目。编译顺序严格来说只有一条铁律:先编译 RT,再编译 DT,最后安装 DT。
RT 包(比如Python4Delphi_RT.dpk)是运行期的核心库,里面是 TPythonEngine、TPythonDelphiVar 这些组件的具体实现;DT 包是设计期支持库,负责把这些组件注册到 IDE 组件面板上,它的编译输出是 BPL,需要右键 Install 才能真正出现在组件栏里。
在 Project Manager 里先选中 RT 包,把活动平台设为 Win64,构建一次;再选中 DT 包,同样构建,然后右键 Install。这一步的关键是平台别选错。Delphi 12.3 的 IDE 是 64 位的,DT 包必须是 Win64,否则 IDE 会直接拒绝加载,报“not compatible with the current IDE”之类的错误。RT 包则看你的目标程序架构,如果程序要同时出 32 位和 64 位版本,两个平台都构建一遍。
2.3 用msbuild命令行编译,更快更可控
如果你和我一样不喜欢在 IDE 里反复点击构建,也可以用命令行直接编:
msbuild Python4Delphi.groupproj /p:Configuration=Release /p:Platform=Win64 /t:Build这个命令会把项目组里 RT 和 DT 一起编出来。实测下来,命令行编包的好处是输出清晰,失败时能直接看到是哪个 pas 文件编译报错,比在 IDE 的 Messages 窗口里翻日志直观得多。不过要注意,运行这条命令之前必须先把 Delphi 的环境变量导入到当前 cmd,最简单的方式是打开 “RAD Studio Command Prompt”(开始菜单里自带),再执行 msbuild。
2.4 验证安装结果:组件栏里有Python4Delphi吗
安装完成后,新建一个 Delphi 项目,组件面板上会多出一页Python4Delphi,里面至少能看到这些东西:
- TPythonEngine:一切的核心,负责加载 DLL 和管理解释器生命周期
- TPythonDelphiVar:向 Python 暴露 Delphi 变量/对象的桥梁
- TPythonGUIInputOutput:把 Python 的 print 输出导向 Delphi 界面控件
验证是否真的装成功,最快的办法是拖一个 TPythonEngine 到窗体上,设置DllPath为D:\Python312,DllName为python312.dll,运行期调用PythonEngine1.ExecString('print("hello from python")'),然后在 TPythonGUIInputOutput 关联的 Memo 里看到输出,就说明整条链路已经通了。
如果这一步输出报错或者没有反应,先别急着怀疑控件坏了,大概率是 DLL 没加载成功或者路径不对。
2.5 组件装好却“丢”了:IDE里控件不显示的排查思路
很多人在 Delphi 里折腾过第三方控件,热搜里也总能看到“控件版本问题导致每次进入 IDE 都丢失控件”这种问题。P4D 虽然没这么严重,但如果你安装后重启 IDE,发现组件栏的 Python4Delphi 页面不见了,可以从三个方向查:
- 检查 Design Time 包有没有被勾选:菜单 Component > Install Packages,看到列表里是否有 Python4Delphi 相关条目,复选框必须打勾。
- 看 BPL 的输出路径:Tools > Options > Library > Library 选项里,Package output directory 如果没设置,DCU 和 BPL 可能散落在各个项目目录里,IDE 启动时找不到就加载不了。统一把 BPL 输出到一个固定目录,比如
C:\Users\你的用户名\Documents\Embarcadero\Studio\23.0\Bpl,能解决大部分“重启就丢”的问题。 - 确认是否同时存在多个版本冲突:如果以前装过旧版 P4D,新版又装了一遍,两个设计期包可能互相抢注册信息。先卸载旧的、删除旧的 BPL 文件,再装新的。
3. 引擎、变量、输出:P4D三个核心组件的运行机制
3.1 TPythonEngine:从DLL加载到解释器初始化
TPythonEngine 是整个 P4D 的入口。它的职责可以理解成“把 CPython 的 C API 翻译成 Delphi 能用的组件属性和事件”。
核心属性就这么几个:
DllName:要加载的 DLL 名称,默认python312.dll,如果装了别的版本就改。DllPath:DLL 所在的绝对路径。如果不设置,系统会按 PATH 搜索顺序找 DLL,这可能带来“本机能跑、换台机器就崩”的隐患。AutoLoad:默认 False,建议运行时在 FormCreate 里把它设为 True,或者手动调LoadDll。设计期保持 False 可以避免 IDE 环境下找不到 DLL 时一直弹错。AutoFinalize:程序退出时自动释放解释器,保持 True 就行。PythonOK:运行期判断解释器是否成功加载,调试时很有用。
一个最小初始化模板:
procedure TForm1.FormCreate(Sender: TObject); begin PythonEngine1.DllPath := 'D:\Python312'; PythonEngine1.DllName := 'python312.dll'; PythonEngine1.AutoLoad := True; PythonEngine1.LoadDll; if PythonEngine1.PythonOK then PythonEngine1.ExecString('print("engine is ready")'); end;这里要提醒一句:LoadDll加载的是 Python 解释器的 C 接口,如果 DLL 加载失败,检查的重点不是 Delphi 代码,而是 python312.dll 是否存在、位数是否匹配、依赖的 VC 运行库是否缺失。后面第 5 章专门讲排查。
3.2 执行Python代码:ExecString、EvalString和ExecStrings
P4D 提供了几条执行脚本的 API,核心区别在于“要不要返回值”。
ExecString只执行,不返回结果,适合跑一段完整的脚本:
PythonEngine1.ExecString( 'import os' + sLineBreak + 'print(os.getcwd())' );EvalString会执行表达式并返回结果,返回类型是Variant,可以接进 Delphi 变量:
var v: Variant; begin v := PythonEngine1.EvalString('2 ** 10'); ShowMessage(VarToStr(v)); // 1024 end;如果要执行多行脚本,建议用 TStringList 把代码装好,然后ExecStrings:
var sl: TStringList; begin sl := TStringList.Create; try sl.Add('def add(a, b):'); sl.Add(' return a + b'); PythonEngine1.ExecStrings(sl); finally sl.Free; end; end;需要注意的是,Python 里函数定义、缩进、空行这些对换行符和缩进非常敏感。我用 TStringList 时,每行代码不要带多余的前导空格,函数体里的缩进统一用四个空格,否则很容易触发IndentationError。
3.3 TPythonDelphiVar:双向变量通道
TPythonEngine 自己提供了一组方便的变量读写方法,最典型的是SetVarValue和GetVarValue:
PythonEngine1.SetVarValue('num', 42); PythonEngine1.SetVarValue('name', 'delphi'); ShowMessage(VarToStr(PythonEngine1.EvalString('name + str(num)')));这适合临时传一两个简单变量。但如果你的数据交换是持续、双向的,更优雅的做法是用TPythonDelphiVar。这个控件本质上是在 Python 命名空间里注册了一个“Delphi 变量”,Python 脚本读写这个变量时,会分别触发 Delphi 侧的OnGetVarValue和OnSetVarValue事件。
procedure TForm1.pyVarGetVarValue(Sender: TObject; var Value: Variant); begin Value := Edit1.Text; // Python 里读取这个变量时,拿到的是 Delphi 输入框内容 end; procedure TForm1.pyVarSetVarValue(Sender: TObject; var Value: Variant); begin Memo1.Lines.Add(Value); // Python 里给这个变量赋值时,Delphi 收到通知 end;对应 Python 脚本:
print(delphi_input) # 触发 OnGetVarValue delphi_output = "from python" # 触发 OnSetVarValue这套机制很有用。比如 Delphi 负责采集用户输入,Python 脚本负责处理,处理完再把结果“写回” Delphi 界面。整个过程只需要在界面上放一个 TPythonDelphiVar,设置好VarName,剩下的逻辑全在脚本里,真正做到了界面和逻辑解耦。
3.4 TPythonGUIInputOutput:让print输出直接上控件
Python 的print默认是往标准输出写的。在控制台程序里没问题,但在 GUI 程序里,标准输出基本是黑洞,你什么也看不见。TPythonGUIInputOutput 就是解决这个问题的:它接管 Python 的 stdout/stderr,把输出重定向到 Delphi 的 Memo 或任何能显示文本的控件。
把 TPythonGUIInputOutput 拖到窗体上,设置Memo属性指向目标 Memo,再把 TPythonEngine 的IO属性指向这个 InputOutput 控件。这样脚本里所有的 print、报错堆栈都会实时显示在 Memo 里。
这个组件对调试特别重要。我建议配置完引擎的第一时间就把 IO 接上,否则脚本一旦抛异常,Delphi 这边只给一个“invalid argument”之类的笼统提示,根本定位不到问题,而 IO 接上之后,Python 的 Traceback 会原样输出到界面,哪一行出错一目了然。
4. Delphi和Python之间的数据流:从简单传参到完整小工具
4.1 传参的几种姿势,以及它们的类型边界
P4D 的跨语言数据交换,底层走的是 Variant 到 Python 对象的转换。简单类型(整数、浮点数、字符串、布尔值)的转换非常透明,直接 SetVarValue 就能过去,Python 侧拿到的就是对应的 Python 类型。
但复杂类型要小心。Variant 数组传到 Python 侧会被转成元组,TStringList 传过去会变成列表,但如果你传一个 Delphi 类实例对象,Python 侧默认拿到的是一个包装对象,做不了太多事情。有几种绕法:
- 简单数据:
SetVarValue('x', 42),直接传。 - 列表/元组:用 Variant 数组传。
- 结构化数据:最省心的是统一转成 JSON 字符串。
JSON 是我在新老项目里用得最多的方案,因为它能把“语言无关”做到极致。Delphi 侧用System.JSON生成 JSON 字符串,Python 侧json.loads解析;Python 侧处理完再json.dumps回去,Delphi 这边用TJSONObject.ParseJSONValue解开。全程不需要关心 Python 的对象模型和 Delphi 的 Variant 类型如何互相映射,只要字符串没编错,数据就丢不了。
4.2 Delphi生成JSON,Python解析,再回传结果
下面这个例子完整演示了 JSON 双向交换:
var jo: TJSONObject; jsonStr: string; resultStr: string; begin jo := TJSONObject.Create; try jo.AddPair('name', 'Leo'); jo.AddPair('count', TJSONNumber.Create(42)); jsonStr := jo.ToString; finally jo.Free; end; PythonEngine1.SetVarValue('json_str', jsonStr); PythonEngine1.ExecString( 'import json' + sLineBreak + 'data = json.loads(json_str)' + sLineBreak + 'data["double_count"] = data["count"] * 2' + sLineBreak + 'print(data)' + sLineBreak + 'result_json = json.dumps(data)' ); resultStr := PythonEngine1.EvalString('result_json'); Memo1.Lines.Add(resultStr); end;这套模式非常稳定。不管 Python 那边怎么操作字典、列表、嵌套结构,最终交给 Delphi 的永远是一个可以控制的 JSON 字符串。分隔符、日期格式、浮点精度这些容易被忽略的问题,也都能在 JSON 层统一处理。
4.3 实战场景一:MD5计算
很多 Delphi 项目有算 MD5 的需求,如果项目用的是老版本 Delphi,没有现成的 THashMD5,写起来要么调第三方库,要么自己实现算法。而在 P4D 环境下,这是 Python 标准库几行的事:
import hashlib print(hashlib.md5("hello".encode("utf-8")).hexdigest())在 Delphi 里调用:
PythonEngine1.ExecString( 'import hashlib' + sLineBreak + 's = "hello"' + sLineBreak + 'print(hashlib.md5(s.encode("utf-8")).hexdigest())' );把 IO 组件接上,输出直接进 Memo。这不只是省了算法实现的麻烦,更重要的是,当你需要 md5、sha256、sha1 多种摘要算法时,Python 标准库全覆盖,Delphi 侧代码几乎不用改。
4.4 实战场景二:执行系统命令并拿到输出
Delphi 执行 DOS 命令拿返回值,传统做法是 CreateProcess 加管道,代码量不小。P4D 环境下直接用 subprocess:
import subprocess r = subprocess.run( ["cmd", "/c", "dir", "/b", r"d:\temp"], capture_output=True, text=True, encoding="utf-8" ) print(r.stdout)capture_output=True会拿到标准输出,r.stdout就是命令执行结果,比 Delphi 的管道 API 简单直观得多。需要注意的是编码,Windows 命令行输出可能不是 UTF-8,Python 侧在subprocess.run里可以指定encoding='gbk'来避免乱码。
4.5 实战场景三:批量文件重命名小工具
综合上面的思路,我用一个完整的小例子收尾:Delphi 负责选择文件夹,Python 负责批量重命名所有 jpg 文件,在文件名前加pic_前缀。
Delphi 侧:
var folder: string; begin if SelectDirectory('选择文件夹', '', folder) then begin PythonEngine1.SetVarValue('folder', folder); PythonEngine1.ExecString( 'import os' + sLineBreak + 'folder = folder.replace("\\\\", "/")' + sLineBreak + 'for name in os.listdir(folder):' + sLineBreak + ' old = os.path.join(folder, name)' + sLineBreak + ' if os.path.isfile(old) and name.lower().endswith(".jpg"):' + sLineBreak + ' os.rename(old, os.path.join(folder, "pic_" + name))' ); ShowMessage('done'); end; end;注意这里我把folder作为变量传入 Python,脚本里用os.path.join拼路径,避免手动处理反斜杠转义。实际跑的时候建议先把目标文件夹里的文件列出来 print 一次,确认无误再执行重命名。这种“先看后改”的习惯能救你很多次。
4.6 实战场景四:Memo数据导入Excel
Delphi 里把 Memo 数据导入 Excel,传统做法是 OLE 调用 Excel COM,慢且容易崩。P4D 环境下,交给 Python 的 openpyxl:
import openpyxl wb = openpyxl.Workbook() ws = wb.active lines = memo_text.splitlines() for i, line in enumerate(lines, start=1): cells = line.split("\t") for j, val in enumerate(cells, start=1): ws.cell(row=i, column=j, value=val) wb.save(r"d:\temp\output.xlsx")Delphi 侧只需要把 Memo 的文本放进变量memo_text:
PythonEngine1.SetVarValue('memo_text', Memo1.Text); PythonEngine1.ExecString(pyScript);Memo 里一行一个记录,字段之间用 Tab 分隔,Python 侧 split 后就整齐地写入 Excel 单元格。这种方式比 OLE 调用稳定得多,也不会因为用户机器上 Excel 版本不同而出兼容问题。
5. 实战踩坑:版本、DLL、打包、线程一个都不能少
5.1 版本兼容矩阵:P4D的master分支不是万能的
很多人拿到 python4delphi-master.zip 就默认它能直接编译。实际上 P4D 的 master 分支对 Delphi 版本的适配是逐步推进的,不同 Delphi 版本对应的 dpk 文件在Packages\Delphi下可能有不同目录。Delphi 12.3 属于较新的版本,如果你下载的 master 快照时间比较早,可能没有现成的 12.3 包,需要自己调整。
我当时整理了这么一张对应表,仅供参考:
| Delphi 版本 | P4D 版本建议 | 编译注意事项 |
|---|---|---|
| Delphi 10.4 | 早期提交或特定分支 | 包目录老,可能需要改条件编译 |
| Delphi 11.x | master 较新提交 | 平台选择 Win64 |
| Delphi 12.0/12.1 | master 更新提交 | DT 包需要匹配 IDE 位数 |
| Delphi 12.3 | master 最新提交 | 实测 Win64 DT 正常 |
选错版本最典型的表现是:RT 包能编译,但 DT 包安装时提示版本不匹配,或者组件栏出现但拖到窗体上 IDE 直接崩。遇到这种情况,先看 master 的 Packages 目录里有没有 12.3 对应目录;如果没有,最粗暴的方式是切到官方最新的 release tag,或者看 Demos 里有没有覆盖 12.3 的更新说明。
5.2 python312.dll加载失败的排查链路
这是 P4D 最常见的坑,而且报错往往很笼统,比如“无法定位程序输入点”或者“LoadLibrary 失败”。我的排查顺序已经固化下来:
- 确认 DLL 文件存在:
D:\Python312\python312.dll是否存在。 - 确认位数匹配:Delphi 程序是 Win32,那 DLL 必须是 32 位 Python 安装目录下的;Win64 程序对应 64 位。混用会直接报加载失败。
- 确认 DllPath 设置正确:如果没设 DllPath,只靠系统 PATH,机器上装多个 Python 版本时很可能加载到错误的 DLL。
- 确认 VC 运行库:Python 3.x 的 DLL 依赖 VC 2015+ 运行库,目标机器没装的话,加载一样失败。
- 看 Windows 事件日志:如果眼前实在没有头绪,打开事件查看器看 Application 日志里的错误模块,能定位到具体是哪个 DLL 依赖项缺失。
排查时可以把DllPath临时写死成绝对路径,排掉所有环境变量因素,等确认无误再改回动态路径。
5.3 分发时目标机器没有Python环境,怎么打包
如果程序要发给客户,客户机器上八成没有 Python。打包思路是把 Python 运行时“随身携带”。有两种做法:
第一种是在安装程序里打一个完整的 Python 安装包,装上之后再跑主程序。优点是省心,缺点是体积大、安装慢、还可能被安全软件拦。
第二种是用官方提供的 embeddable package。Python 官网下载页每个版本都有Windows embeddable package (64-bit),它是一个很小的 zip,解压后包含python312.dll和Lib目录,专门用于嵌入场景。把解压后的内容放到主程序目录下,比如.\runtime\,然后在 Delphi 侧配置:
PythonEngine1.DllPath := ExtractFilePath(Application.ExeName) + 'runtime\'; PythonEngine1.PyHome := ExtractFilePath(Application.ExeName) + 'runtime\';embeddable package 默认不会装 pip,也不带 site-packages,所以如果要用第三方包,得先把包装到一个临时环境里,再把对应文件拷贝进去,或者修改_pth文件加上自定义路径。这个细节容易忽略,我一开始直接拿 embeddable package 装上就跑,结果脚本 import 第三方包全失败,后来才发现是标准库路径没对上。
5.4 多线程调用的正确姿势
P4D 的 PythonEngine 不是天然线程安全的。如果 UI 线程执行一条耗时的 Python 脚本,界面会直接卡死,这体验很糟糕;反过来,如果在后台线程里操作主线程创建的 PythonEngine,经常会出现诡异的内存错误。
我的经验是三个原则:
- 全程序只保留一个全局 PythonEngine 实例,避免多个解释器实例互相干扰。
- 所有跨线程的 Python 调用,统一用 TCriticalSection 串行化,保证同一时刻只有一个线程在解释器里执行。
- 长任务放到后台线程,但只在后台线程内部调用 PythonEngine,主线程通过 TThread.Synchronize 或消息机制收结果。
调度的最小模板:
procedure TMyThread.Execute; begin FCriticalSection.Enter; try PythonEngine1.ExecString('long_running_task()'); finally FCriticalSection.Leave; end; end;P4D 支持多线程的深度比这复杂得多,但 95% 的项目用“单引擎 + 互斥锁”的模式就够了,稳定第一。别为了一点性能去挑战多解释器多线程并发,那是个大坑。
5.5 性能边界和优化思路
跨语言调用的性能,卡在类型转换上。Delphi 的 Variant 转 Python 对象、Python 对象转 Variant,都有封装开销。所以有一条铁律:批量处理数据,不要在循环里频繁穿越语言边界。
错误示范:在 Delphi 循环里逐行调用 PythonEngine 处理数据,一行一次调用,一万行就是一万次转换,慢到怀疑人生。
正确做法:把整个数组或列表一次性传过去,在 Python 侧循环处理完,再一次性传回来。哪怕中间用 JSON 字符串转一次,速度也远快于逐行调用。
大数据量的场景,我试过把数据写成临时文件,让 Python 读文件,处理完再写结果文件,Delphi 读回。虽然多了一次 IO,但避免了大规模 Variant 数组在内存里来回拷贝,实测反而更稳。
最后补一个实用技巧:Demos目录比文档值钱
我在踩了无数坑之后回头发现,P4D 真正最好的文档不是 Docs 目录,而是 Demos。它覆盖了引擎初始化、变量传递、GUI 输出、多线程、自定义 Python 类型等几乎所有常见用法,而且每个 Demo 都是一个能直接跑的最小工程。装好控件后,别急着写业务代码,花半天时间把 Demos 挨个跑一遍,比自己瞎调试高效得多。
如果你问我现在手上维护的老 Delphi 项目值不值得装 P4D,我的答案是:如果需求只是“加一个脚本执行入口”,那用外部进程调 python.exe 可能更省事;但如果你已经动了“把业务规则外置”“用 Python 生态补齐 Delphi 短板”的心思,这个 zip 包就是你能找到的最顺滑的桥。编译安装的折腾很快,之后每一次脚本热更新带来的便利,都是实打实的回报。
本文还有配套的精品资源,点击获取