使用P4D 编写Python Extension

使用P4D 编写Python Extension

  转自:http://1000copy.itpub.net/post/10379/276310

Author: 1000copy
Address: 空无一人 不知所致
Contact: [email protected] or http://1000copy.itpub.net
organization: 无党派人士
status: 差不多了:version: 1:copyright: This document has been placed in the public domain. You may do with it as you wish. You may copy, modify, redistribute, reattribute, sell, buy, rent, lease, destroy, or improve it, quote it at length, excerpt, incorporate, collate, fold, staple, or mutilate it, or do anything else to it that your or anyone else's heart desires.
Dedication: 喜欢Python的Delphi的程序员
abstract: 在互联网公共可访问领域内,关于Python/C interface的介绍,手册都是 比较多的。Py直接支持C编写扩展,对于Delphi程序员,P4D是一个很好的 选择。不幸的是,通过P4D[#]编写PyExtention,并没有一个很好的入门文 档,本文试图填写这个空白。本文风格完全模仿Writing Python Extensions[1],希望以例子为本,让大家很快的进入状态。

引言

本文假设你

  • 懂得Python
  • 懂得Delphi
  • 想要通过P4D编写Python Extension
  • 已经安装了Delphi7,P4D,Python2.4以上。

第一个Python Extension

以下的例子是可以直接使用的,只要拷贝如下代码,存放到ExAdd.dpr,直接用Delphi编译,就可以成为一个Python Extension 。 我们可以首先看到效果,然后在分析程序。

最小的例子

最小的例子:

{文件名 ExAdd.dpr}
library ExAdd;
uses SysUtils,Classes,PythonEngine;
{$E pyd}
var
FModule : TPythonModule;
FEngine:TPythonEngine ;
function Add( Self, Args : PPyObject ) : PPyObject; far; cdecl;
var
a, b : Integer;
begin
with GetPythonEngine do
begin
if PyArg_ParseTuple( args, 'ii:Add', [@a, @b] ) <> 0 then
begin
Result := PyInt_FromLong( a + b );
end
else
Result := nil;
end;
end;
procedure initExAdd; cdecl;
begin
FEngine := TPythonEngine.Create(nil);
FModule := TPythonModule.Create(nil);
FModule.Engine := FEngine;
FModule.ModuleName := 'ExAdd';
FModule.AddMethod( 'exadd', @Add, 'exadd(a,b) -> a+b ' );
FEngine.LoadDll;
end;
exports
initExAdd;
var
OldExitProc: pointer;
procedure MyExitProc;
begin
FModule.Free;
FEngine.Free;
ExitProc := OldExitProc;
end;
begin
OldExitProc := ExitProc;
ExitProc := @MyExitProc;
end.
// 测试代码
//from ExAdd import *
//print exadd(1,10)

解说

这是一个最小的例子,只要一个文件ExAdd.dpr ,不需要任何其他的Pas Unit文件就可以了。 当我们把他放到py的syspath内,比如<pythonhome>libsite-packages,在pywin内,可以做如下测试:

>>> from ExAdd import *
>>> print exadd(1,10)
11
>>>

可以看到,Python内的程序确实成功的调用了通过Delphi写的扩展。如何做到的?

如何注册一个模块

当Python内执行

from ExAdd import *

时,将会到syspath内寻找ExAdd.pyd,这里的pyd就是一般的dll,只不过还有一些约定。 当Py找到这个文件后,就调用引出函数initExAdd,这个函数的命名就是python程序和.pyd模块的的一个约定----函数命名必须为init+module名称。 一般来说,在init函数内,就进行引擎的初始化,模块的注册,函数,类型的注册等等工作。这里例子内,我们使用了TPythonEngine, TPythonModule两个P4D提供的类,帮助我们做这些工作。 注册模块时,要注意

FModule.ModuleName := 'ExAdd';

内的ModuleName就是在Python内使用的模块完全一致,当然我们可以使用其他的名字,比如ExQuickAdd,只要

from ExAdd import *

内使用的模块名称一致即可。为了方便和一致,我们可以约定dll的名字,python内的module,delphi内的TPythonModule名字完全一致。 这在语法上并非必须,不过这样做是一个很好的习惯。

如何注册一个函数

任何一个按照如下原型注册的函数,都可以被注册为PyExtention的函数。

function Add( Self, Args : PPyObject ) : PPyObject; far; cdecl;

其中cdecl说明服从C语言的调用规范,而不是Pascal或者其他。毕竟Python是C语言写就的,当然按照C语言的习惯来。 这个函数原型中,参数将会包括Self,Args,返回值得也是一个PPyObject,熟悉Python语言的都知道,任何一个Python函数在被调用时都会传递一个Self 指针进来,并且以Tuple的方式传递参数列表,这个Add函数的实现约定上也就表现出来了,所有的类型都是对象。比如Add(3,4)这个的Python调用,参照Add在Delphi中函数原型, 上,那么"3,4"作为一个Tuple对象,伴随Self,也是一个PPyObject,返回值7也是一个PPyObject来表达。 要不怎么都说Python慢呢?本来一个加操作可以直接对应汇编中的一个指令,现在又是对象又是指针,当然很难快了。

一旦有了这样的声明,就可以这样注册函数。

FModule.AddMethod( 'exadd', @Add, 'add(a,b) -> a+b ' );

以上语句向Python系统声明,exadd函数的实现在add内,最后参数作为__docstring__。当IDE内使用这个函数时,可以通过codeinsight,或者help来获得函数的使用说明。 现在来看add的实现代码。 一眼看过去,PyArg_ParseTuple,PyInt_FromLong是两个特别的东西。 PyArg_ParseTuple负责把传进来的args变成简单的Delphi类型,在Ppyobject内存储的3,4,分别存放到a,b:Integer内,就是

PyArg_ParseTuple( args, 'ii:Add', [@a, @b] ) <> 0

其中第二个参数 'ii:Add' ,有些像是Format格式,i指明类型为Integer,两个I指明有两个整数,:add是可选的,当出错的时候,有:add,可以帮助程序员更好的找到错误。 这样就把PPyobeject所表达的PythonType转为一般Delphi类型; 而PyInt_FromLong这是想法,他把Delphi的Long类型转换为PyObject的Integer;从而可以让结果可以为Python识别。 这两个函数尽管是P4d实现的,但是和Python/C interface手册内规定的函数名称一致,因此具体的调用方法也可以看Python/C interface手册。

实际上Python实现内的对象表达采用了一个结构(Struct),很有一些复杂,我们现在可以在很高层的去看,要感谢P4D所做的工作。

实现一个类

第一个例子可以工作,并且能够演示注册模块,函数和一些基本的Python Ext的概念。 对于长期使用Delphi这样的OO语言,仅仅公开函数当然不够方便,我们需要的是全OO编程,即使跨越了语言,也不会放弃这样的习惯。 我们现在要让Delphi的类可以为Python。

又一个例子

你首先看到的依然是一个例子,我们要把Delphi中的TPoint公开出来,让python可以调用,模块名称为dpoint,最终我们要在pythonIDE内看到的效果:

>>> from dpoint import *
>>> print SmallPoint(222,111)
<SmallPoint at D393C8>
>>> SmallPoint.__doc__
'wrapper for Delphi TPoint typen'

P4D为注册类这样的工作提供了TPyDelphiWrapper类,在这个例子里,我们围绕这TPyDelphiWrapper来分析。

例子代码:

library dpoint;
uses
Sharemem ,SysUtils,Classes,WrapDelphi,Types,PythonEngine;
{$E pyd}
var
FModule : TPythonModule;
FEngine:TPythonEngine ;
FDelphiWrapper : TPyDelphiWrapper;
procedure initdpoint; cdecl;
begin
FEngine := TPythonEngine.Create(nil);
FModule := TPythonModule.Create(nil);
FModule.Engine := FEngine;
FModule.ModuleName := 'dpoint';
FDelphiWrapper := TPyDelphiWrapper.Create(nil);
FDelphiWrapper.Engine := FEngine;
FDelphiWrapper.Module := FModule;
FEngine.LoadDll;
end;
exports
initdpoint;
var
OldExitProc: pointer;
procedure MyExitProc;
begin
FModule.Free;
FEngine.Free;
ExitProc := OldExitProc;
end;
type
TPyDelphiPoint = class(TPyObject)
private
fValue: TPoint;
protected
public
constructor CreateWith( APythonType : TPythonType; args : PPyObject ); override;
class procedure SetupType( PythonType : TPythonType ); override;
end;
Type
TTypesRegistration = class(TRegisteredUnit)
public
function Name : String; override;
procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;
end;
function TTypesRegistration.Name: String;
begin
Result := 'Types';
end;
procedure TTypesRegistration.RegisterWrappers(APyDelphiWrapper: TPyDelphiWrapper);
begin
inherited;
APyDelphiWrapper.RegisterHelperType(TPyDelphiPoint);
end;
constructor TPyDelphiPoint.CreateWith(APythonType: TPythonType;
args: PPyObject);
var
x, y : Integer;
begin
inherited;
if APythonType.Engine.PyArg_ParseTuple( args, 'ii:Create', [@x, @y] ) <> 0 then
begin
fValue.X := x;
fValue.Y := y;
end
end;
class procedure TPyDelphiPoint.SetupType(PythonType: TPythonType);
begin
inherited;
PythonType.TypeName := 'SmallPoint';
PythonType.TypeFlags := PythonType.TypeFlags + [tpfBaseType];
PythonType.DocString.Text := '12345';
end;
begin
RegisteredUnits.Add(TTypesRegistration.Create);
OldExitProc := ExitProc;
ExitProc := @MyExitProc;
end.

注册过程

一个类必然要属于某一个模块,注册一个类就涉及到注册一个模块。关于注册模块,在例子中占据了不少带代码,但是它和第二部分完全一样,我们掠过不看。 本来注册一个类是有些复杂度的,如果想要知道这个复杂度,可以先看看参考文献1内的描述。不过采用P4D的类型注册框架就简单多了。 我们的例子pyd命名为dpoint ,我们准备把TPoint类型公开到Python内。

在initdpoint函数内,TPythonEngine,TPythonModule照样的初始化,比起函数注册来说,不同的地方在于创建了TPyDelphiWrapper的实例gDelphiWrapper, 并且指明他所属的PythonEngine,PythonModule:

procedure initdpoint;
begin
gEngine := TPythonEngine.Create(nil);
gEngine.AutoFinalize := False;
gModule := TPythonModule.Create(nil);
gModule.Engine := gEngine;
gModule.ModuleName := 'dpoint';
gDelphiWrapper := TPyDelphiWrapper.Create(nil);
gDelphiWrapper.Engine := gEngine;
gDelphiWrapper.Module := gModule;
gEngine.LoadDll;
end;

gDelphiWrapper将会在RegisteredUnitList寻找RegisteredUnit,并且调用 这个类别内的RegisterWrappers方法,通过这个方法或者需要注册的Python类的Delphi包装类。 因此,我们要做的事情就是: 约定实现两个类,一个是需要公开的类型的Wrapper,这里就是TPyDelphiPoint,一个是注册这个Wrapper的注册类,本例子内就是TTypesRegistration。 TTypesRegistration只要实现两个覆盖基类的方法,从而达到通知TPyDelphiWrapper需要注册的类是TPyDelphiPoint。

function Name : String; override; procedure RegisterWrappers(APyDelphiWrapper : TPyDelphiWrapper); override;

我们更多的注意力,尤其是以后的更多对PythonExtension特性的利用,集中于TPyDelphiPoint上。 TPyDelphiPoint,作为一个PythonType,最少要实现的方法有:

      constructor CreateWith( APythonType : TPythonType; args : PPyObject ); override;
class procedure SetupType( PythonType : TPythonType ); override;

我们可以注意到,CreateWith传递的args依然是PPyObject类型,和前文谈到的add方法对参数和返回值的处理都是一致的。 SetupType将会指明在Python内如何使用这个类型,根据源代码知道,SetupType指明这个类型在Python内的类型为SmallPoint,提供基本服务(fvbase),类型文档__doc__为 '12345', 测试用例3.1代码如果正常运行,就自然的证实了这一点。

充分利用Python的特性

repr服务

以上例子很简单,但是可以表达主旨,是进一步了解和把握P4D编写扩展的基础。 从3.1的测试用例看:

>>> print SmallPoint(222,111)
<SmallPoint at D393C8>

这样的输出很不友好,我们希望他是这样的:

>>> print SmallPoint(222,111)
222,111

这样的服务在py内早已存在,它的名字叫做repr,每个对象如果希望打印友好,都应该支持这样的服务。 在Delphi编写的Py扩展中,如何做到这样的效果?

例子

一旦框架铺陈完毕,编写具体的功能就很简单了。repr服务只要覆盖一个方法,加上对返回参数的包装就可以了:

function  Repr : PPyObject; override;
..
implementation
..
function TPyDelphiPoint.Repr: PPyObject;
begin
with GetPythonEngine do
Result := PyString_FromString(PChar(Format('<Point (%d, %d)>', [Value.X, Value.Y])));
end;

更多

设置属性,需要覆盖RegisterGetSets方法:

class procedure TPyDelphiPoint.RegisterGetSets(PythonType: TPythonType);
begin
inherited;
with PythonType do
begin
AddGetSet('X', @TPyDelphiPoint.Get_X, @TPyDelphiPoint.Set_X,
'Provides access to the X coordinate of a point', nil);
AddGetSet('Y', @TPyDelphiPoint.Get_Y, @TPyDelphiPoint.Set_Y,
'Provides access to the Y coordinate of a point', nil);
end;
end;

别忘了在SetupType内加入一行:

PythonType.Services.Basic := PythonType.Services.Basic+[bsGetAttrO, bsSetAttrO];

告诉Python你的服务中有属性的支持。

允许dpoint之间比较大小,需要覆盖Compare方法:

function TPyDelphiPoint.Compare(obj: PPyObject): Integer;
var
_other : TPyDelphiPoint;
begin
if IsDelphiObject(obj) and (PythonToDelphi(obj) is TPyDelphiPoint) then
begin
_other := TPyDelphiPoint(PythonToDelphi(obj));
Result := CompareValue(Value.X, _other.Value.X);
if Result = 0 then
Result := CompareValue(Value.Y, _other.Value.Y);
end
else
Result := 1;
end;

同样别忘了在SetupType内加入一行:

PythonType.Services.Basic := PythonType.Services.Basic+[bsCompare];

告诉Python你的服务中有bsCompare的支持。

编写扩展后做什么?

  1. P4d的代码值得已读,因为
  • 基于注册的架构,dll直接到类TDynamicDll值得看,了解Python的内部实现,P4d本身就是Python和Delphi结合编程的良好榜样。
  • 可以很好的了解Python类型的内部实现,比如整数的结构里面有那些字段,有那些基本类型服务的方法
  1. 使用P4D还可以怎么样?
这个图景在我自己还并不很清楚。但是Python和Delphi的结合的愿望在我却一直很强烈,希望有更多的朋友参与进来,提出更多的想法。

参考

[1] Michael Hudson 's writingPythonExtensions

[2] P4D Python For Delphi的缩写,从它的Change.txt内可以看到,这个P4D从1998年计算有接近10年的历史了。世道沧桑啊。

[3] Using Delphi and Python together pythonDelphiTalk .

很好的教程,可惜对输出类型这里语焉不详,不知道为什么。这也是我要写本文的动因。

Delphi = a great object oriented language, and a fantastic RAD environment and framework. You can build excellent GUI's in Delphi that run very fast and have a small .EXE footprint. Runs on linux too. There is a free version of Delphi, too.

Python = is rapidly rising in popularity due to its pseudo-code-like, syntactically minimalist but straightforward syntax. Like the Delphi community, the Python community of programmers has a nice feel about it, and it is committed to developing rich, high quality, open-source class libraries for Python - without the hype of Java.

This paper will demonstrate how to incorporate Python in any of your Delphi apps! Or conversely, how to use Delphi as a GUI for your python apps.

—Andy Bulka

你可能感兴趣的:(python,function,Integer,Delphi,extension,wrapper)