Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих и не только


Информационный Канал Subscribe.Ru

По материалам сайта www.gigabyte.iatp.org.ua

Внедрение питона в Delphi

Внедрение тех или иных средств расширения функциональности разрабатываемых Вами приложений уже давно вышло за пределы чего-то отдаленного и недоступного. А сегодня с растущей популярностью языка Python соответственно расширяются и сферы эго применения. И если Microsoft и Open Office используют бейсик-подобный язык макрокоманд то ничего не мешает нам использовать Питон в своих приложениях для тех же целей.

Кому это выгодно?

Естественный ответ на этот вопрос: Всем.

Во-первых это выгодно Вам как разработчику приложения так как единожды написав базовый интерфейс программы Вы сможете по мере необходимости добавлять в него соответствующую функциональность без перекомпиляции всего приложения. А если при этом вы еще и используете один из популярных языков для написания плагинов, то сможете и вовсе не беспокоиться о написании дополнительных модулей так как использование макроязыков снимает необходимость в компиляции кода, а соответственно отчасти лишает вас необходимости сидеть над отладкой и настройкой внешних модулей и их <привинчиванию> к главной программе.

Во-вторых это выгодно заказчику. Так если у него в наличии есть нормальный программист, то он сам сможет добавить недостающую функциональность базовому приложению и тем самым настроить его под себя, что в конечном итоге позитивно скажется на эффективности работы программы, и, опять таки, снимет с Вас большую часть головной боли по поддержке и сопровождению Вашего программного продукта.

Джентльменский набор инструментов Для внедрения макропитона в разрабатываемое вами приложение нам потребуется:

  • Свежий интерпретатор Python, который можно найти на сайте www.python.org или на КП диске;
  • Портированные с C файлы хедеров Python API, которые вы можете либо преобразовать вручную или с помощью одной из утилит h2pas или же загрузить уже готовые порты для Delphi например с адреса: http://membres.lycos.fr/marat/delphi/PythonForDelphi.exe (или же на КП диске)
  • Ну и естественно Delphi. Здесь без него никак не обойтись.

После инсталляции интерпретатора и Delphi-портов у вас в арсенале Delphi-компонентов появится новая палитра компонентов "Python" с 8-ю новыми компонентами которые отвечают за соответствующую часть работы (см. таблицу).

Описание компонентов входящих в состав пакета PythonForDelphi
Название компонентаОписание
TPythonGUIInputOutput, TPythonInputOutputТранзитные компоненты для вывода результатов выполнения команд питона в указанный текстовый компонент
TPythonEngine"Движок" питона. Компонент который собственно и отвечает за выполнение макрокоманд
TAtomPythonEngineНаследник TPythonEngine отвечающий за обработку команд переданных посредством COM - технологии.
TPythonTypeКомпонент отвечающий за создание и обработку новых типов данных в Python.
TPythonModuleКомпонент отвечающий за создание и обработку новых модулей в Python
TPythonDelphiVarКомпонент представляющий собой переменную Python.
TPythonDatabaseКопмонент предоставляющий возможность доступа к базам данных подключенных к Delphi-приложению из макросов Python.

Привет мир!

Не нарушая традиций ознакомления с языками программирования мы сперва поприветствуем весь мир с тем что наша программа заработала. Для этого мы после запуска Delphi кинем на форму нового приложения два компонента TMemo, а также компоненты TPythonEngine и TPythonGUIInputOutput для вывода результата (см.рис.1). последующая настройка потребует от нас минимума напряга мозгов и не больше ловкости рук для определения компонента Memo1 для вывода результата (см. рис. 2 левый) и подстановки в TPythonEngine компонента TPythonGUIInputOutput для вывода результата (см. рис. 2 правый). Далее, в реакции кнопки на нажатие записываем следующий код:


procedure TForm1.Button1Click(Sender: TObject);
begin
 PythonEngine1.ExecStrings(Memo2.Lines);
end;
И запускаем программу. (см. рис. 3-4).


Использование модулей

Один из важнейших элементов будущего расширяемого приложения - объектная модель т.е. совокупность тех специфических методов (объектов, свойств и т.д.) которые по существу выполняют базовые функции и предоставляют макросам доступ тем или иным базовым функциям главного приложения. Одним из простейших методов реализации собственной объектной модели в Python - это использование модулей. Для создания модуля мы воспользуемся компонентом TPythonModule. Перекинув его с палитры компонентов, и установив соответствующие значения его свойств: Engine := PythonEngine1, ModuleName :='mymath' мы тем самым создали новый Python-модуль который в тексте макроса вызывается через команду:import mymath,а используется командой: mymath.<имя функции>.

Для добавления функции осуществляется в событии TPythonModule.OnInitialization посредством метода AddDelphiMethod:
function AddDelphiMethod(MethodName:PChar;DelphiMethod:TDelphiMethod;DocString:PChar) : PPyMethodDef;

    где
  • MethodName - название метода в Python;
  • DelphiMethod - ссылка на метод который будет вызван главной программой
  • DocString - строка, комментирующая данную функцию.

Реализация добавления метода к Python-модулю осуществляется так:


procedure TForm1.PythonModule1Initialization(Sender: TObject);
begin
 with (Sender as TPythonModule) do
  AddDelphiMethod('power',PyPower,'Delphi Power method');
end;
сам же метод реализуется в одном из компонентов Delphi и подлежит строгой типизации на основе шаблона:
TDelphiMethod = function ( self, args : PPyObject ) : PPyObject of object; cdecl;
Где self - ссылка на объект вызвавший данный метод, args - ссылка на переданные этому методу параметры.

Сама реализация этого метода выглядит в Delphi выглядит следующим образом:


function TForm1.PyPower(pself,args:PPyObject):PPyObject;
var base,exponent:Extended;
    V:Variant;
begin
 with GetPythonEngine do
  begin
   V:=PyObjectAsVariant(args);
   base:=V[0];
   exponent:=V[1];
   Result:=PyFloat_FromDouble(Power(base,exponent));
  end;
end;
Здесь во первых через глобальную функцию GetPythonEngine вызывается Python-интерпретатор, дальше идет преобразование аргументов переданных методу в тип Variant с последующим вычислением степени и выводом результата.

В тексте Python-макроса вызов этого метода будет выглядеть следующим образом:


Import mymath;
Var1 = mymath.power(2,2);
Var2 = mymath.power(2,8);
Print Var1*Var2;
А результат выполнения данной команды показан на рисунке 5.

Использование переменных

Наиболее простым способом доступа к тем или иным параметрам главного приложения из макросов есть использование переменных. Для создания Python-переменных в Delphi используется специальный компонент TPythonDelphiVar. С его помощью создается глобальная или модульная переменная произвольного типа чтение и запись которой вызывают соответствующую реакцию главного приложения. При создании Python-переменной нам следует перекинуть на форму компонент TPythonDelphiVar и заполнить его свойство VarName значением которое в среде Python будет интерпретироваться как переменная. Если при этом оставить значение свойства module по умолчанию (__main__), то будет создана глобальная переменная. Если же задать значение свойства отличное от определенного по умолчанию, то будет создана модульная переменная и доступ к ней будет возможен, после импортирования соответствующего модуля.

Чтение и запись переменной реализуется через соответствующие события компонента TPythonDelphiVar. Если при этом мы записываем простые (ординарные) данные, то можно использовать события OnGetData и OnSetData, если же используются более сложные типы (классы, словари и т.п.) то рекомендуется использовать события OnExtGetData, OnExtSetData.

Для примера мы введем в нашу объектную модель модульную переменную buttonname модуля mymath которая будет отвечать за изменение названия нашей кнопки (Button1). Приведенный ниже код показывает как реализуется обработка чтения и записи в эту переменную:


procedure TForm1.PythonDelphiVar1GetData(Sender: TObject;
  var Data: Variant);
begin
 Data:=Button1.Caption;
end;

procedure TForm1.PythonDelphiVar1SetData(Sender: TObject; Data: Variant);
begin
 Button1.Caption:=Data;
end;

procedure TForm1.PythonDelphiVar1Change(Sender: TObject);
begin
 Button1.Caption:=(Sender as TPythonDelphiVar).ValueAsString;
end;
Этот код должен быть занесен в события OnGetData, OnSetData и OnChange соответственно. Использование же этой переменной в Python-макросе осуществляется следующим образом:

import mymath;
print mymath.buttonname;
print mymath.buttonname.Value;
mymath.buttonname.Value="Testing";
,а результат выполнения этой команды показан на рис.6.

Как видим название кнопки изменилось с <Button1> на <Testing>. Хочу заметить, что при обращении к переменной только по ее имени вы получите информационное значение: а при попытке записи в переменную через ее имя у вас скорее всего выскочит исключение Access Violation. Так что будьте осторожны при работе с переменным и помните, что чтение и запись значений переменных осуществляется через конструкцию <имяпеременной>.Value.

Создание новых типов данных

Если типы данных используемые для работы с макросами в Python полностью не удовлетворяют ваших потребностей, то создать новый тип данных, который бы реализовывал недостающие возможности встроенных типов данных. Для этого предназначен компонент TPythonType. А его использование приведено в листинге. По сути для реализации нового типа данных в главной программе создается объект - наследник TPyObject в котором реализуются все необходимые функции (в нашем случае суммирование и вычитание комплексных чисел). После этого в специальных методах класса регистрируются все возможные операции над этим типом данных после чего с ним можно работать в интерпретаторе. (см.рис.7)

Заключение

Этих базовых возможностей при правильной реализации будет вполне достаточно для того, чтоб организовать хорошо документируемый, макроязык на основе Python, который сможет не только <выводить числа> в текстовое поле, но также создавать свои формы, диалоги, кнопки и др., а изучив синтаксическую мощь и логическую простоту языка Python вы превратите процесс усовершенствования вашего приложения в легкий и приятный труд.

Под конец хочу Вас предупредить, что для нормальной работы приложений использующих Python как макроязык Вам следует учесть что у большинства пользователей он (дистрибутив Python) не установлен, так что при инсталляции вам следует это учесть и заранее запихнуть в инсталляционный пакет подходящую версию интерпретатора.


unit Main;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, PythonEngine, PythonGUIInputOutput, StdCtrls;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Memo2: TMemo;
    Button1: TButton;
    PythonGUIInputOutput1: TPythonGUIInputOutput;
    PythonEngine1: TPythonEngine;
    PythonModule1: TPythonModule;
    PythonDelphiVar1: TPythonDelphiVar;
    PythonType1: TPythonType;
    procedure Button1Click(Sender: TObject);
    procedure PythonModule1Initialization(Sender: TObject);
    procedure PythonDelphiVar1GetData(Sender: TObject; var Data: Variant);
    procedure PythonDelphiVar1SetData(Sender: TObject; Data: Variant);
    procedure PythonDelphiVar1Change(Sender: TObject);
    procedure PythonType1Initialization(Sender: TObject);
  private
    function PyPower(pself,args:PPyObject):PPyObject;cdecl;
    { Private declarations }
  public
    { Public declarations }
  end;

  TPyComplex = class(TPyObject)
    re,im:Extended;
    Name:String;
    constructor Create(APythonType:TPythonType);override;
    constructor CreateWith(PythonType:TPythonType;args:PPyObject);override;
    function Repr:PPyObject;override;
    class procedure RegisterMethods(PythonType:TPythonType);override;
    class procedure RegisterMembers(PythonType:TPythonType);override;
    class procedure RegisterGetSets(PythonType:TPythonType);override;
    procedure Add(ARe,AIm:Extended);
    procedure Sub(ARe,AIm:Extended);
     function DoAdd(args:PPyObject):PPyObject;cdecl;
     function DoSub(args:PPyObject):PPyObject;cdecl;
  end;
var
  Form1: TForm1;

implementation
uses Math;
{$R *.dfm}

function TForm1.PyPower(pself,args:PPyObject):PPyObject;
var base,exponent:Extended;
    V:Variant;
begin
 with GetPythonEngine do
  begin
   V:=PyObjectAsVariant(args);
   base:=V[0];
   exponent:=V[1];
   Result:=PyFloat_FromDouble(Power(base,exponent));
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
 PythonEngine1.ExecStrings(Memo2.Lines);
end;

procedure TForm1.PythonModule1Initialization(Sender: TObject);
begin
 with (Sender as TPythonModule) do
  AddDelphiMethod('power',PyPower,'Delphi Power method');
end;

procedure TForm1.PythonDelphiVar1GetData(Sender: TObject;
  var Data: Variant);
begin
 Data:=Button1.Caption;
end;

procedure TForm1.PythonDelphiVar1SetData(Sender: TObject; Data: Variant);
begin
 Button1.Caption:=Data;
end;

procedure TForm1.PythonDelphiVar1Change(Sender: TObject);
begin
 Button1.Caption:=(Sender as TPythonDelphiVar).ValueAsString;
end;

procedure TForm1.PythonType1Initialization(Sender: TObject);
begin
PythonType1.PyObjectClass:=TPyComplex;
end;

constructor TPyComplex.Create(APythonType:TPythonType);
begin
inherited Create(APythonType);
re:=0;
im:=0;
end;

constructor TPyComplex.CreateWith(PythonType:TPythonType;args:PPyObject);
var V:Variant;
begin
inherited;
 with GetPythonEngine do
  begin
   V:=PyObjectAsVariant(args);
   re:=V[0];
   im:=V[1];

  end;
end;

function TPyComplex.Repr:PPyObject;
begin
 with getPythonEngine do
  Result:=VariantAsPyObject(Format('(%3.2f %3.2f)',[re,im]));
end;

function TPyComplex_GetName(obj:PPyObject;context:Pointer):PPyObject;cdecl;
begin
with GetPythonEngine do
 Result:=PyString_FromString(PChar(TPyComplex(PythonToDelphi(Obj)).Name));
end;

function TPyComplex_SetName(obj,value:PPyObject;context:Pointer):integer;cdecl;
begin
 with GetPythonEngine do
  begin
   if PyString_Check(value) then
    begin
     TPyComplex(PythonToDElphi(Obj)).Name:=PyObjectAsString(Value);
     Result:=0;
    end
   else
    begin
     Result:=-1;
     PyErr_SetString(PyExc_AttributeError^,'attribute Name expected a string value');
    end;
  end;
end;

class procedure TPyComplex.RegisterMethods;
begin
inherited;
 with PythonType do
  begin
   AddMethod('Add',@TPyComplex.DoAdd,'PyComplex.Add(re,im)');
   AddMethod('Sub',@TPyComplex.DoSub,'PyComplex.Sub(re,im)');
  end;
end;

class procedure TPyComplex.RegisterMembers;
begin
inherited;
 with PythonType do
  begin
   AddMember('re',mtDouble,Integer(@TPyComplex(nil).re),mfDefault,'Real part');
   AddMember('im',mtDouble,Integer(@TPyComplex(nil).im),mfDefault,'Imaginary part');
  end;
end;

class procedure TPyComplex.RegisterGetSets;
begin
inherited;
 with PythonType do
  AddGetSet('Name',TPyComplex_GetName,TPyComplex_SetName,'Name of number',nil);
end;

procedure TPyComplex.Add(Are:Extended;AIm:Extended);
begin
 RE:=Re+ARE;
 Im:=Im+AIm;
end;

procedure TPyComplex.Sub(ARe:Extended;AIm:Extended);
begin
Re:=RE-ARE;
Im:=Im-AIm;
end;

function TPyComplex.DoAdd(args:PPyObject):PPyObject;
var V:Variant;
begin
with GetPythonEngine do
 begin
  V:=PyObjectAsVariant(args);
  Add(V[0],V[1]);
  Result:=ReturnNone;
 end;
end;
function TPyComplex.DoSub(args:PPyObject):PPyObject;
var V:Variant;
begin
with GetPythonEngine do
 begin
  V:=PyObjectAsVariant(args);
  Sub(V[0],V[1]);
  Result:=ReturnNone;
 end;
end;
end.
©Gigabyte 2005

Subscribe.Ru
Поддержка подписчиков
Другие рассылки этой тематики
Другие рассылки этого автора
Подписан адрес:
Код этой рассылки: comp.soft.prog.programmershelp
Отписаться
Вспомнить пароль

В избранное