
简介这是一套演示如何在 Delphi 环境中调用 Python 结巴分词库的完整源码示例面向需要做中文文本处理、NLP 预处理的 Delphi 开发者。项目基于 PythonForDelphi 组件搭建 Delphi 与 Python 的桥接并附带结巴分词核心模块、测试脚本及 UI 表单清晰展示从界面触发到底层分词调用的完整链路。压缩包共 78 个文件涵盖 Delphi 工程文件dpr/dproj/dfm/pas、Python 源码与编译缓存py/pyc/p、结巴词典与配置txt/conf、DLL 运行库等包体约 13.59MB并附可直接运行的 EXE 便于对照效果。已有 536 人学习下载。通过该例子可掌握 PythonForDelphi 的集成方法、Delphi 与 Python 数据类型传递以及结巴分词精确模式、全模式等实际调用适合入门级与中级 Delphi 开发者快速上手中文分词应用开发。 Delphi接Python做分词这事我是被逼的。前几年维护一套老Delphi写的进销存系统客户提了个需求把上万条商品备注做词频统计还要支持按关键词模糊检索。Delphi本身做中文分词那就是硬刚正则拆一拆、按标点切一切遇到“南京市长江大桥”这种歧义直接歇菜。折腾一圈后发现Python社区的结巴分词jieba最成熟、用的人最多索性就在Delphi进程里嵌了一个Python解释器把文本丢给它切完再拿回来。整个过程踩了不少坑今天把能直接跑的源代码和排查思路都整理出来。1. 为什么非要在Delphi里嵌Python而不是自己写分词先说结论如果你的项目只是处理几十条固定文本那不建议上混编方案杀鸡不用牛刀。但一旦文本量大、词类杂、还要求持续维护词表自研分词的代价就会高到让你怀疑人生。中文分词真正的难点不在“按词切”而在“两可”的歧义和词库更新。比如“羽毛球拍卖完了”人可以自然分成“羽毛球拍 卖完了”规则要写得兼顾所有场景复杂度直接爆炸。结巴分词的好处在于三件事第一词库积累厚除了通用词还有不少领域词日常、IT、财经基本覆盖第二它支持精确模式、全模式、搜索引擎模式满足不同场景第三接口简单到一行代码就能拿结果。对我来说它最值钱的是解决歧义的能力这在纯Delphi代码里几乎无解。当然混编也有代价安装环境多一套、部署时要带Python运行时、还多出几十MB内存开销。但换来的能力是碾压级的这点代价值。2. 开工前的环境准备最容易翻车的都在这里我用的组合是 Windows 10 Delphi 11.3 Python 3.10 x64 Python4Delphi 最新master分支。整体版本要求不高Delphi 10.4及以上都能用Python 3.8到3.12都行。下面是完整清单组件建议配置备注Delphi10.4 / 11 / 12版本越新越省心Python3.10 x6432位机选32位见下方陷阱jieba0.42.1pip install jieba 即可Python4DelphiGitHub master需要编译安装Python4Delphi的安装步骤分四步走从GitHub clone python4delphi仓库。进入 Packages 目录用Delphi打开对应版本的 .dproj 工程。在Delphi里先Build再Install让组件注册到IDE。安装成功后组件面板会出现 Python 分类包含 TPythonEngine、TPythonGUIInputOutput、TPythonModule 等组件。装完组件后别忘了在 Project - Options 里把 Python4Delphi 的 Source 目录加入 Search Path否则编译自己工程时找不到单元。这里有个最容易翻车的点Python 位数必须和 Delphi 编译目标位数一致。Delphi 默认编译成 32 位程序那就必须装 32 位 Python如果你用 64 位 Python 去配 32 位 Delphi 程序报错信息往往是“Python initialization failed”第一眼会以为初始化代码写错了实际是DLL加载根本没成功。我建议直接让Delphi编译成64位然后统一装64位Python现在Windows上跑64位没有任何障碍。Python侧只需要一行命令装依赖pip install jieba装完在命令行验证一下能import再继续python -c import jieba; print(jieba.__version__)3. 完整源代码Delphi 结巴分词最小可运行示例先说明一下我的实现思路为什么中间要加一层JSON而不是直接操作Python对象。Python4Delphi确实可以直接访问Python的list或dict但那样要写大量 PyObject 操作代码类型转换繁琐还要手动管理引用计数稍有不慎就内存泄漏。不如让Python把分词结果转成JSON字符串Delphi侧解析JSON数组。这是典型的人与人语言不通就写纸条传话简单可靠。3.1 Python侧封装脚本首先写一个jiebawrap.py把分词包装成函数# -*- coding: utf-8 -*- import json import jieba def segment(text): if not text: return [] # 精确模式lcut 直接返回 list比 cut 更省事 return jieba.lcut(text, cut_allFalse)这个文件放哪个目录都行后面统一在Delphi里把目录加进sys.path。如果你要自定义词典在这里加一行jieba.load_userdict(my_dict.txt)自定义词典的格式是“词语 词频 词性”三列词频可以省略词性也非必须纯文本每行一个词就行。3.2 Delphi侧界面与组件在Delphi窗体上放这些组件TPythonEngine命名为 PythonEngine1TPythonGUIInputOutput命名为 PythonGUIInputOutput1两个TMemommoInput、mmoResult一个TButtonbtnSegment组件连接很关键把 PythonEngine1 的 IO 属性指向 PythonGUIInputOutput1这样Python的print输出会重定向到界面控件调试起来能看见Python侧到底发生了什么。Delphi单元完整代码如下unit uJiebaMain; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.Memo, Vcl.ExtCtrls, PythonEngine, PythonGUIInputOutput, System.JSON; type TfrmJieba class(TForm) pnlTop: TPanel; btnSegment: TButton; mmoInput: TMemo; mmoResult: TMemo; PythonEngine1: TPythonEngine; PythonGUIInputOutput1: TPythonGUIInputOutput; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure btnSegmentClick(Sender: TObject); private FPythonReady: Boolean; procedure SetupPython; function DoJiebaSegment(const AText: string): TStringList; end; var frmJieba: TfrmJieba; implementation {$R *.dfm} procedure TfrmJieba.FormCreate(Sender: TObject); begin FPythonReady : False; try SetupPython; FPythonReady : True; except on E: Exception do ShowMessage(Python初始化失败: E.Message); end; end; procedure TfrmJieba.SetupPython; begin // Python 3.10 对应的 DLL PythonEngine1.DllName : python310.dll; // 自己管生命周期比默认方式更稳定 PythonEngine1.AutoFinalize : False; PythonEngine1.Init; // 把 jiebawrap.py 所在目录加入 Python 搜索路径 PythonEngine1.ExecString( import sys; sys.path.append(rG:\Project\PyBridge) ); // 导入结巴和封装模块 PythonEngine1.ExecString(import jieba); PythonEngine1.ExecString(import jiebawrap); end; function TfrmJieba.DoJiebaSegment(const AText: string): TStringList; var jsonStr: UTF8String; arr: TJSONArray; i: Integer; textObj: PPyObject; begin Result : TStringList.Create; if not FPythonReady or (Trim(AText) ) then Exit; // 把 Delphi 字符串转成 Python 字符串对象放入主模块字典 textObj : PythonEngine1.PyObjectFromString(UTF8String(AText)); PythonEngine1.PyDict_SetItemString(PythonEngine1.ModuleDict, input_text, textObj); // 调用 Python 封装函数 PythonEngine1.ExecString(result jiebawrap.segment(input_text)); PythonEngine1.ExecString(result_json json.dumps(result, ensure_asciiFalse)); // 取回 JSON 字符串 jsonStr : PythonEngine1.EvalString(result_json); // 解析 JSON 数组 arr : TJSONArray.ParseJSONValue(UTF8ToString(jsonStr)) as TJSONArray; if arr nil then begin try for i : 0 to arr.Count - 1 do Result.Add(arr.Items[i].Value); finally arr.Free; end; end; end; procedure TfrmJieba.btnSegmentClick(Sender: TObject); var words: TStringList; i: Integer; begin mmoResult.Clear; if Trim(mmoInput.Text) then begin ShowMessage(请输入文本); Exit; end; words : DoJiebaSegment(mmoInput.Text); try for i : 0 to words.Count - 1 do mmoResult.Lines.Add(Format(%d. %s, [i 1, words[i]])); finally words.Free; end; end; procedure TfrmJieba.FormDestroy(Sender: TObject); begin if FPythonReady then begin PythonEngine1.Finalize; end; end; end.有几点操作说明PyObjectFromString接收的是UTF-8编码的字节串所以传参前用UTF8String(AText)做了一次转换。PyDict_SetItemString(PythonEngine1.ModuleDict, input_text, textObj)等价于在Python主模块里定义一个全局变量input_text后面ExecString里直接引用这个名字。第4步里EvalString(result_json)返回的是字符串形式的结果注意在Delphi里它是UTF-8编码的AnsiString所以要再用UTF8ToString转成Delphi Unicode字符串交给TJSONArray去解析。Python侧ensure_asciiFalse必须写否则拿到的是 \uXXXX 转义序列中文全变乱码。4. 首次跑通的实测记录性能和表现我在自己的机器i5-8400、16GB内存、Windows 10 x64上做了几组测试给还没上车的人一个心理预期测试项耗时/占用Python引擎初始化 首次导入jieba约1.2秒单次分词200字商品描述约30毫秒单次分词1000字长文本约120毫秒进程额外内存占用约25MB第一个1.2秒很多人容易忽略它其实是jieba首次加载词典并构建前缀词典的时间。核心经验是Python解释器和jieba务必常驻不要在每次点击按钮时都初始化一次。上面代码把初始化放在FormCreate里就是这个原因。后续每次分词的耗时主要在Python代码执行本身Python4Delphi调用引擎的开销只有几毫秒基本可忽略。实测下来连续调用非常稳定没有内存明显上涨说明PyObject的引用计数由引擎管理没泄漏。如果你要处理的是几万条文本建议用jieba.enable_parallel(4)开并行分词但注意那是多进程模式Windows下要放在if __name__ __main__保护块里和Delphi混编时配置稍微麻烦小数据量不建议开。5. 替你先踩过的五个大坑逐个排查链路这一节的价值我觉得比源代码本身还大。下面每个问题都是我实际碰到、并且用排查流程定位出来的。5.1 现象Python initialization failed程序起不来排查步骤先确认DllName写对了没Python 3.10对应python310.dll3.11对应python311.dll。再确认DLL能被系统找到把Python安装目录加入Path环境变量或者直接在SetupPython里用SetDllDirectory指定Python目录。最后确认位数Delphi编译目标32/64位要和Python安装版本一致。我那次卡在位数上Delphi默认x86编译Python装了64位折腾了一下午才反应过来。这类错误不会告诉你“位数不符”只给你一个模糊的初始化失败所以排查时直接把它放在最前面。5.2 现象分词结果是乱码或者全是 \uXXXX先看Python侧打印输出是否正常。如果Python侧print出来正常但Delphi界面乱码问题出在字符编码转换链路。结巴分词返回的是Python Unicode字符串json.dumps默认把非ASCII转成 \uXXXX 转义所以必须加ensure_asciiFalse。另一边Delphi从引擎取回字符串本质是UTF-8字节串必须用UTF8ToString转成Delphi string而不是直接赋值给String。如果你在XML配置文件里配置了Python脚本路径还要留意文件本身的编码建议统一存成UTF-8。5.3 现象程序退出时崩溃或者退出时卡住这个和AutoFinalize属性有关。默认情况Python4Delphi会在程序退出时自动Finalize但如果你在关闭窗体时释放了其他组件顺序一乱就容易崩。解决方案就是代码里写的AutoFinalize : False然后在FormDestroy里手动调用PythonEngine1.Finalize。如果还崩就把Finalize放在程序主窗体销毁后的最后一步。5.4 现象Delphi里报 “No module named jieba”但命令行import正常这个坑的根因是当前工作目录不同。命令行运行Python时当前目录是控制台目录Delphi程序启动时当前目录是exe所在目录并不包含jieba的site-packages。排查方法是先让引擎打印sys.pathPythonEngine1.ExecString(import sys; print(sys.path));然后根据输出把site-packages目录或者脚本目录手动append进去。我代码里写的是把G:\Project\PyBridge加进sys.path就是干这个用的。更稳妥的做法是让Delphi动态获取exe所在路径再拼上脚本目录var ScriptDir: string; begin ScriptDir : ExtractFilePath(Application.ExeName) python\; PythonEngine1.ExecString(import sys; sys.path.append(r ScriptDir )); end;5.5 现象界面卡死尤其是处理大量文本时结巴分词是CPU密集操作直接影响主线程。如果是长文本或者批处理建议开一个TThread来跑但注意Python有GIL同一时刻只能一个线程执行Python字节码多线程并不能加速只是把UI卡顿问题转嫁给后台线程。我的做法是主线程创建PythonEngine后台线程里仅做文本读取和结果处理分词调用仍然回到主线程排队执行配合进度提示体验比一把梭好很多。如果你真的有高并发需求正确答案是改用多进程或者干脆把分词服务独立成一个HTTP接口Delphi走REST调用彻底解耦。那是另一个更大的话题了。6. 建议直接复制这套代码的三种场景这套方案我最推荐用在老系统的文本检索增强、词频统计、以及客服问答关键词匹配三个场景。老系统底子是Delphi业务数据都在本地临时去搭一套微服务成本太高但塞一个Python解释器进去很简单。尤其是词频统计你要的其实就是把所有文本分词后拼接成词袋然后用TStringList排序统计结巴输出质量远比自己写的规则好。最后再提醒一句如果你不是特定场景别急着上混编。先拿几十条真实文本测一下分词效果确认结巴的输出风格符合你的业务预期再写代码不迟。毕竟架构越复杂后续维护要背的包袱就越重。本文还有配套的精品资源点击获取