转:Python for delphi教程

 

Python for Delphi
  Andy's Extensions Latest Techniques Deployment Issues Example1

 

bmpDelphiLogo.gif (1781 bytes) +  labslogo (Copy 1).gif (3584 bytes) 

Using Delphi and Python together

Talk for the Australian Delphi User Group (ADUG), July and August 2001
-Andy Bulka
abulka@netspace.net.au

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.

Here is an example of a finished app.

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

Introduction and Tutorial

Introduction

Create classes in python and have Delphi use these classes as if they were native Delphi classes!  Benefit:  python can do all the business logic and hard work, whilst Delphi can be used for the GUI and application framework etc.

The interactive shell and fast development time in python makes building business logic here.  Define your domain classes and build all the units that do work.  Only use Delphi for GUI.

On the other hand, if you want python to talk to and control delphi, then use COM.

Installation

Install Python for win32 Official distribution installer           PythonPoweredSmall.gif (361 bytes)

Then install the Python for Delphi components from
     http://www.multimania.com/marat/delphi/python.htm    

Python for Delphi is a set of components that wrap up the Python 1.5 Dll into Delphi. They let you easily execute Python scripts, create new Python modules and new Python types. You can create Python extensions as Dlls and much more, including being able to create classes in python and have Delphi use these classes as if they were native Delphi classes!
 

installAutoCompile.gif (8516 bytes)

so you finish up with the following palette:

postInstallPalette1.gif (8131 bytes)which are the engine, the atomEngine, pythonDelphiVar, etc.  Note: You only need to use about three of these components to do most of your python-delphi work, the rest are mostly historical or very advanced.

Basic Use - A simple test

A simple Python evaluator:

  1. Create a new Form
  2. Drop a TMemo (or a TRichEdit)
  3. Drop a TPythonGUIInputOutput
    for displaying Python's messages
  4. Drop a TMemo for the source code
  5. Drop a TPythonEngine
  6. Connect the attribute IO of the
    TPythonEngine to the
    TPythonGUIInputOutput.
  7. Connect the attribute Output of
    TPythonGUIInputOutput to
    the TRichEdit.
  8. Drop a TButton and
    call it "Execute script"
  9. Double-click on the button and add:
     PythonEngine1.ExecStrings( Memo1.Lines );    
  1. Write in the Memo1: some python code e.g. print 2+2 or
    that pictured below
  2. Click on the Execute button


simple1.gif (8459 bytes)

click the button and

simple2result.gif (14533 bytes)

python is working with Delphi at a rudimentary level. 

The above demonstration is an example of sending strings of code to the python interpreter and receiving Python standard output.  You can drive python in this way as much as you like, by pumping text commands to it

  PythonEngine.ExecStrings( commandLines )    

The above Delphi component wrapper method is not always convenient, since ExecStrings expects commandLines to be a TStrings type, whereas sometimes you want to send a single string to the python interpreter / engine (and not have to create a stringlist)  So I wrote a wrapper procedure  PyExe (see AndysPythonDelphi extensions) which just takes a string.  You can also send multiple lines to PyExe by separating lines with #13 (carriage return) or linefeed.

  procedure PyExe(cmds: string; 
               engine: TAtomPythonEngine);    

So that you can do things like:

  PyExe('print 5');
  PyExe('print 5+23'+#13+'print "welcome"');
          // note two commands here    

Controlling where Python standard output goes

Every Delphi form must have a PythonEngine component and a PythonInputOutput component.

There are two types of 'PythonIO' components - one that directs all output to a memo or richEdit, or you can use an IO component which lets you define a custom event handler to handle the python output.  You can redirect output to raize codesite or even to the standard Delphi Debug Event Log Window: e.g.

  1. Create a new form, add a PythonEngine component and a PythonInputOutput component.
  2. Wire them together (Python engine's IO property to point to the PythonInputOutput component)
  3. Redirect all python standard output to the standard Delphi debug console, by double clicking on the PythonInputOutput component's OnSendData event and putting in the code OutputDebugString( PChar(Data));

PythonDelphiVars

To communicate between Delphi and Python at a deeper level we use the Delphi PythonDelphiVar component.  It is a piece of Delphi that the python interpreter / engine can actually see!  It looks like this:

pyVar1.gif (5374 bytes)

The most important PythonDelphiVar component property is VarName property which is the name by which python 'sees' this component. 

bullet
The Name property of the PythonDelphiVar component is, of course, how Delphi refers to the PythonDelphiVar component. 
bullet
The VarName property of the PythonDelphiVar component is how the Python side refers to the PythonDelphiVar component.

In the picture above, from Python's point of view, I have called the PythonDelphiVar component VAR1 which means we can write Python code like:

  PyExe('VAR1.Value = 101');    

or in a python script, just simply

  VAR1.Value = 101    

which results in the PythonDelphiVar component having a value of 101.  Delphi can access this component's value with

  PythonDelphiVar1.ValueAsString    

TIP:  In practice, I use the convention of making the name of each PythonDelphiVar component and its VarName property the same.  

Example #1 - Running an entire python program and storing the results in a set of PythonDelphiVar components. 

All Delphi does is trigger off the script (say when a button get hit) and then retieves the results from the PythonDelphiVar components.

The Delphi side:

  1. Create a new form, add a PythonEngine component (name it PE) and a PythonInputOutput component.  Wire them together as previously discussed in controlling where the Python output goes to, above.
  2. Drag down two PythonDelphiVar components and name them HEADERVAR and RESULTVAR.  Set the VarName properties to the same as their names.
  3. Create a button with the code:
procedure TForm2.Button2Click(Sender: TObject);
begin
    PyExeFile('unit2.py', PE);
end;
 

The Python side

  1. Create a python unit called unit2.py and put it in the same folder as your delphi units.  Add the following code to the python unit:
print "Welcome to Python unit2"
HEADERVAR.Value = '----- Welcome -------'
RESULTVAR.Value = 200 * 3
 

Now run the delphi form and press the button.  Our PythonDelphiVar components should be populated.  How do we know? 

  1. Just add another button with the code:
procedure TForm2.Button3Click(Sender: TObject);
begin
  showmessage( HEADERVAR.ValueAsString 
    +#13+ RESULTVAR.ValueAsString );
end;
 

Example #2 - Loading an external python program and calling a function in it, storing the result in a PythonDelphiVar component. 

            The Python side

Modify the unit2.py  python script as follows, to include a function which takes a comma separated list of numbers and returns the sum of those numbers.  The result is stored in a PythonDelphiVar component named RESULTVAR.

import string

def calcAvg(str):
  total = 0
  vallist = map(lambda n: int(n), string.split(str,','))
  for val in vallist:
    total += val
  RESULTVAR.Value = total / len(vallist)


The Delphi side:

procedure TForm2.FormCreate(Sender: TObject);
begin
  PyExeFile('unit2.py', PE);   
   // this loads in the python class
end;
procedure TForm2.Button4Click(Sender: TObject);
begin
	PyExe('calcAvg("1,5,10")', PE);
	showmessage( RESULTVAR.ValueAsString );
end;
 

Now add an enhancement where, on the Delphi side, you allow the user to type in the list he or she wants to calculate the average on.  Add an edit component and a button with the following code. 

procedure TForm2.Button5Click(Sender: TObject);
begin
  PyExe('calcAvg("' + edit1.text + '")', PE);
  showmessage( RESULTVAR.ValueAsString );
end;


pyVar2.gif (1658 bytes)    yields 13.  Success!

Direct access to Python Classes

First we used stdout to communicate between Delphi and Python.

Then we used special pigeon holes ( PythonDelphiVar components ) to communicate between Delphi and Python.

Now we are going to learn how to gain access to actual Python classes and instances, and call the methods of those instances using normal Delphi dot notation.  And of course being able to set and get properties of those instances would be cool too, wouldn't it?

Using TPythonAtom

Basically the technique is to define, in Delphi, some OleVariant variables which hold references to Python objects.  We can then access methods and properties on these python objects using the familiar dot syntax that we use in Delphi (and most other languages) e.g.

  pythonicCustomer.Address := '23 Smith st.' ;
  pythonicCustomer.RunReport() ;    

Official documentation on this technique is found in Demo12 of the examples that come with the Python for Delphi components. "Simply add the PythonAtom in the uses clause, declare a new var of type OleVariant and call the function getAtom( any Python object ). It will return a new OleVariant that will let you access properties or methods very simply, as you would do with Word !" See also latest features. Note: if you don't understand the preceding paragraph, that's ok, since you won't have to know about PythonAtom in the next examples, because I have wrapped the difficult stuff up in a simple function or two.

The Delphi side:

  1. Create a new form, and drag down the pythonAtomEngine component and name it PE.  Drop a TPythonGUIInputOutput for displaying Python's messages. Connect this to your richtext or memo component.  More info on redirecting python IO earlier in this article.
  2. Add AndyDelphiPy, ComCtrls, pythonAtom units to your uses clause, e.g.
implementation

uses AndyDelphiPy, ComCtrls, pythonAtom;

var
   aCustomer : OleVariant;
 
  1. Then in the formshow or formCreate event add the code that loads the main unit of your python code..
	procedure TfrmMain.FormShow(Sender: TObject);
	begin
		PyExeFile('YourPythonApp.py', PE);    

At this point the python interpreter has loaded and executed your python classes and any code in the unit you are loading.  Now we can create instances of python classes in Delphi and store the references to them in variants.

	aCustomer := PyClass('Customer()', pdv, PE);

The above code shows how to instantiate a python class Customer and store that reference in a Delphi variant aCustomer

The pdv is a necessary intermediate python delphi var used for the instantiation and is not used subsequently.  Drag and drop a PythonDelphiVar component and name it "pdv" (the pdv simply stands for python delphi var) and set the Varname property to "pdv" [ ASIDE:  ** Matthew Vincent. suggested during the talk that I could perhaps create this pdv component within my PyClass wrapper function, thus eliminating the pdv parameter from the PyClass function.  I couldn't get this to work, as the pythonDelphiVar component seems to need to be created at design time - creating it at runtime with the form as the owner worked, but didn't actually satisfy the python engine.  Even if this would have worked, we would still have to specify the owning form as a paramter to PyClass, which means we would have gained nothing in terms of the number of parameters we would have had to pass.... ]

Note also that  the PyClass delphi function is another one of the AndysPythonDelphi extensions which simplifies the process of instantiating a python class from Delphi.  Normally you have to deal with a couple of lines of code and some reference counting, in order to instantiate a python class.  By using my wrapper function PyClass the process is reduced to a simple one line call.

  1. When this particular example delphi app runs, after the execution of the FormShow event, the customer instance will have been created and will be ready to be used. Let's add a button so that we can manipulate that instance.  Create a button and add the following code:
  aCustomer.Surname := 'Bloggs' ;
  aCustomer.Address := '23 Smith st.' ;
  inc( aCustomer.NumOrders ); 
  showmessage( 'Customer info: ' + 
      aCustomer.RunReport() );

That's it for the Delphi side of things. 

The Python side:

Now let's work on the python side of things, which only involved creating a single python unit named YourPythonApp.py in the same folder as your delphi app.  

class Customer:
  def __init__(self, surname=''):
    self.Surname = surname
    self.Address = 'unknown address'
    self.NumOrders = 0
  def RunReport(self):
    return 'Customer ' + self.Surname + 
     ' of ' + self.Address + 
     ' has made ' + `self.NumOrders` +
     ' orders so far.'
 

Note that the method __init__ in the above class is the  constructor method (like .Create in Delphi).

Running the Delphi app and clicking the button should give you:

finalVariantsuccess.gif (27441 bytes)

Success!

Passing python objects as parameters

Your Delphi code can now interact seamlessly with python objects.  One last thing to watch out for is the situation where your delphi code passes a python instance around as a parameter to other python methods, you cannot simply pass the variant reference e.g.

  aCustomer.AddOrder(anOrder)  # won't work    

instead you must pass the pure python reference.  So define a python method in each python class called something like 'myself' e.g.

  class Order:
  ...
    def myself(self):
      return self    

then you will be able to successfully:

  aCustomer.AddOrder(anOrder.myself)   	# works    

If you are lucky, you may only need to define the 'myself' function just once, perhaps in some Python base class.  And of course you can call the function anything you like, just don't call it 'self' since that already reserved by both Delphi and Python.

Deployment

Easy - no registry settings or anything fancy.

As well as your compiled delphi executable, just add the python dll (only about 500 k) in the folder containing your app, plus a Lib folder containing any extra python units you are using.  For example random.py and whrandom.py could be placed into the Lib folder if you had used them.  The examples used in this article did not use any extra units (the string unit that we used for string.split is a "built in" unit and so does not need to be supplied with your app).

deploy1.gif (2433 bytes) 
          plus

 deploy2.gif (1854 bytes)

Advanced topic: Here is a discussion and tips on python for delphi deployment issues.

Andy's helpful minor extensions to Python-for-Delphi

These are Andy's extra high-level wrapper functions used and described in this article.  These functions use and augment the standard Python-for-Delphi components.

More info on python

If you need further informations on Python, visit the official Web site at http://www.python.org/

Andy's Easy Tips on Reading Python Code

Python is a simple, straightforward and elegant language. It uses standard conventions of accessing methods and properties and is fully OO. Types are associated with objects not variables, so you don’t need to declare variables. Functions are called like afunction(param1, param2) and objects are created from classes the same way e.g. o = MyClass(). Python is case sensitive.

There are no begin end reserved words or { } symbols in Python to indicate code blocks – this is done through indentation. The colon in if lzt: simply means ‘then’. The idiom of testing objects (rather than expressions) in an if statement makes sense as python treats empty lists, None and 0 as false.

Python understands named parameters e.g. In a method call, afunction(From=None) means you are passing None (null / nil) as the ‘From’ parameter, whilst in a method definition From=None means that if the caller does not supply this parameter, then it will default to None.

The first argument of all methods defined inside classes must be ‘self’. This argument fills the role of the reserved word this in C++ or Java or self in Delphi. Most languages supply this parameter (a reference to the current instance) implicitly whereas Python makes this concept explicit. At runtime this parameter is supplied by the system, not the caller of the method, thus the def AddOrder(self, order) method in reality takes one parameter when calling it: AddOrder( order ).

The statement pass means do nothing.

You can return multiple items at once e.g. return (2, 50) and also assign multiple items at once e.g. x, y, z = 0 or even expressions like result, status, errmsg = myfunction(1, 90).

Other class files/modules are imported using import somefile. __init__ methods are simply constructors. Finally, a lambda is just a one line function that reads functionname = lambda paramlist : returnedexpression.

Both Python and JPython (Java Python, now called Jython) are open source, free and available from http://www.python.org/

youneedpython2.gif (5396 bytes) :-)

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

 

Andy's Extensions
Home Up

 

Andy's helpful minor extensions to Python-for-Delphi

These are Andy's Delphi wrapper functions used and described in this article. 

These functions make use of and augment the standard Python-for-Delphi components.

Save the following Delphi code as AndyDelphiPy.pas and use this unit as necessary
when programming with Python-for-Delphi components .
Of course, ensure it is in your delphi compilation library path.

unit AndyDelphiPy;          
interface          
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, PythonEngine, AtomPythonEngine, Rzcsintf, ComCtrls, pythonAtom;          
  procedure PyExe(cmds: string; engine: TAtomPythonEngine);
  procedure PyExeFile(fp: string; engine: TAtomPythonEngine);
  function  PyClass(pyclass: string; pydelphivar : TPythonDelphiVar; engine: TAtomPythonEngine): OleVariant;
  function  PyVarToAtom(pydelphivar : TPythonDelphiVar; engine: TAtomPythonEngine): OleVariant;
  procedure PyConsoleOut(const Data: String);          
implementation          
procedure PyExe(cmds: string; engine: TAtomPythonEngine);
var
  s: TStringList;
begin
  s := TStringList.create;
  try
    s.text := cmds;
    engine.ExecStrings( s );
  finally
    s.free;
  end;
end;          
procedure PyExeFile(fp: string; engine: TAtomPythonEngine);
var
  s: TStringList;
begin
  s := TStringList.create;
  try
    if pos(':\', fp) = 0 then
      fp := ExtractFilePath(Application.ExeName) + fp;
    s.LoadFromFile( fp );
    engine.ExecStrings( s );
  finally
    s.free;
  end;
end;          
function PyVarToAtom(pydelphivar : TPythonDelphiVar; engine: TAtomPythonEngine): OleVariant;
var
  v: PPyObject;
begin
  v := pydelphivar.ValueObject;
  result := getAtom(v);
  GetPythonEngine.Py_XDECREF(v);
end;          
function PyClass(pyclass: string; pydelphivar : TPythonDelphiVar; engine: TAtomPythonEngine): OleVariant;;
begin
  PyExe(pydelphivar.VarName + '.Value = ' + pyclass, engine);
  result := PyVarToAtom(pydelphivar, engine);
end;          
procedure PyConsoleOut(const Data: String);
begin
  OutputDebugString( PChar(Data));
end;          
end.
        

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

 

Latest Techniques
Home Up

 

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

Latest Python Delphi techniques & developments

As of October 2001 a totally new way of talking to python we developed, within the Python for Delphi framework.  You use variants, and get smart references to python objects.

So whilst before you could create a reference to an instance to a class, in an olevariant.  Now we use variants.

Also we can instantiate classes directly (without needing to use my PyClass() function etc. etc.

Here is what Morgan, one of the authors of the components has to say:
  http://groups.yahoo.com/group/pythonfordelphi/message/210

The latest way of programming in python for Delphi if you have Delphi 6 and python 2.1 or higher.

-Andy Bulka.

These techniques are demonstrated in Demo25 in the examples folder of your Python for Delphi distribution.

The old vs. the new ways.

Because Delphi 6 has custom variants, they can point to specific smart proxies for python objects.  Before Delphi 6, you could have an oleVariant pointing to a python instance, but you couldn't do the smart things like know that it was a list type etc.

The following examples assume you have a module called LoginMgr.py in the python path or in the same folder as your application  .exe that you have built in Delphi.  Note that the python file LoginMgr.py contains within it a python class called LoginMgr which has a method called somemethod.

Old way   (see my basic tutorial for more info 
on the AndyDelphiPy functions.)
New way  (requires Delphi 6)
// Drop a TPythonAtomEngine onto your form 
// or datamodule and name it PE
// You also need to drop a pythonDelphiVar 
// component and call it pdv

uses AndyDelphiPy;

var
  obj : olevariant;
begin

AndyDelphiPy.PyExeFile('LoginMgr.py', PE);
obj  := AndyDelphiPy.PyClass('LoginMgr()', pdv, PE);
obj.somemethod()    // call the method

// Drop a TPythonAtomEngine or TPythonEngine 
// onto your form or datamodule.

uses VarPyth;

var
  mymodule, obj: variant;
begin

mymodule := Import('LoginMgr');
obj := mymodule.LoginMgr();
obj.somemethod()    // call the method

Note that it it possible to slightly mix the old and new way, so that if you use the AndyDelphiPy.PyExeFile('LoginMgr.py', PE); to import the module then you can then switch to the new way, declare an obj: variant; then instantiate an instance using obj := MainModule.LoginMgr();  However you still need Delphi 6 and so you might as well just use the new way properly.

Widestrings

Declare your delphi strings widestrings if you want to get more than 255 chars back from calls to python methods that return strings. e.g.

var
   s : widestring;
begin
  s := mypythonclassinstance.somemethod() ;
 showmessage(s) ;

Booleans

If your python method call returns 1 or 0 and this is supposed to be interpreted as a boolean, then cast it inside Delphi e.g.

if Boolean( mypythonclassinstance.somemethod()) then
    ....

Accessing syspath directly

Here is a function that accesses a global variable called SysModule and access the syspath directly.

This function also calls VarIsPythonSequence which tests to see if the parameter passed is a list or not.

procedure TForm1.Button2Click(Sender: TObject);
const
  LIB = 'E:\\ZopeWebSite\\bin\\lib';
  LIBDLL = 'E:\\ZopeWebSite\\bin\\DLLs';
var
  re : variant;
  m : variant;
begin
  memo1.lines.Add('SysModule.path is ' + SysModule.path);
  memo1.lines.Add('');
  Assert(VarIsPythonSequence(SysModule.path));
  displaySysPath(ListBox1);    
  if not Boolean(SysModule.path.Contains(LIB)) then
    SysModule.path.insert(0,LIB);
  SysModule.path.append(LIBDLL);
  memo1.lines.Add('SysModule.path now is ' + \
                   SysModule.path + #13#13);
  displaySysPath(ListBox1);    
  fixSysPath;    
  re := Import('re');
  showmessage(re);    
  m := Import('xml.dom.minidom');
  showmessage(m);    
end;    

Playing with sys paths

This is an example of how to set the python system path as seen by delphi's instance of the python interpreter (as represented by the pythonEngine component).  Note that it is imperative that you have \\ as the slashed in your path as otherwise things like \fred will actually be interpreted as \f (whatever that escaped character is) plus 'red'. 

Technique 1

procedure TForm1.fixSysPath;
const
  LIB = 'E:\\ZopeWebSite\bin\\lib';
  LIBDLL = 'E:\\ZopeWebSite\\bin\\DLLs';
begin    
  // this is illegal
  // SysModule.path.Clear;       
  // this will work with latest python for delphi components OK.
  //SysModule.path := NewPythonList;       
  // this is a boring but effective solution as well.
  while SysModule.path.Length > 1 do   
    SysModule.path.pop;    
  SysModule.path.append(LIBDLL);
  SysModule.path.append(LIB);
end;    

Technique 2

procedure TForm1.btnClearSyspathToJustLibClick(Sender: TObject);
var
  currdir, libdir : string;
begin
  currdir := ExtractFilePath( Application.ExeName );    

NOTE: Simply putting a window path as the currdir will ultimately fail since the paths returned by Delphi have single slashes and python needs wither unix slashes or \\ slashes. See here for an algorithm to handle this.

  libdir := EnsurePathHasDoubleSlashes(libdir);    
  libdir := currdir + 'Lib';    
  SysModule.path := NewPythonList;      
  // Relies on Jan 2002 install of python for Delphi components    
  SysModule.path.append(currdir);
  SysModule.path.append(libdir);
end;    

NOTE:  See the python for delphi deployment section for a more in-depth discussion of paths.

Supplimentary utility to display the python syspath in a delphi gui control.

procedure TForm1.btnDisplaySysPathClick(Sender: TObject);
begin
  ListBox1.clear;
  displaySysPath(ListBox1);
end;

Writing a Delphi function that uses a python function to do the hard work.

Here is an example of writing a delphi utility function that takes a string, and splits it up (delimited by comma) and puts the result into a delphi list box.  We are using python split function to do the splitting - cool eh?

procedure TForm1.splitAstring(str:string; lstbox: TListBox);
var
  s, lzt : variant;
  i : integer;
begin
  s := VarPythonCreate(str);   
          // convert normal string into a python string.
  lzt := s.split(',');    
  for i := 0 to lzt.Length-1 do
    lstbox.Items.Add(lzt.GetItem(i))
end;    

Displaying the python syspath in a delphi listbox

Even though we have a pointer to a python list object (via a Delphi variant), we still have to call .GetItem(i) on a python list rather than the python syntax of lzt[i] - why?  Because we are in Delphi and thus we cannot use python syntax. 

procedure TForm1.displaySysPath(lstbox: TListBox);
var
  lzt : variant;
  i : integer;
begin
  Assert(VarIsPythonSequence(SysModule.path));
  lzt := SysModule.path;
  for i := 0 to lzt.Length-1 do
    lstbox.Items.Add(lzt.GetItem(i));
  lstbox.Items.Add('----------------------------------');
end;    

Loading python base64 and minidom module and processing XML in Delphi

procedure TForm1.minidomLoadClick(Sender: TObject);
var
  m, doc, top : variant;
  s : string;
begin
  fixSysPath;
  displaySysPath(ListBox1);    
  m := Import('base64');
  showmessage(m);
  s := m.encodestring('this is some text which I am going to encode then decode again.');
  showmessage(s + #13+#13 + m.decodestring(s));    
  m := Import('xml.dom.minidom');
  doc := m.Document();
  showmessage(doc);
    
  top := doc.createElement( 'Workspace' );
  top.setAttribute('Version', '1.1 beta4');
  doc.appendChild(top);    
  s := doc.toxml();
  showmessage('doc.toxml()' + #13+#13 + s);    
end;    

Importing your own class

Ensure you have a TPythonAtomEngine or TPythonEngine onto your form or datamodule.

var
  mymodule, obj: variant;
begin    
mymodule := Import('LoginMgr');
obj := mymodule.LoginMgr();
obj.somemethod()    // call the method    

Morgan's original tutorial on the new techniques

VarPyth unit:

It wraps a Python object and lets you act on it (call methods or use properties) like you would do in Python, and like you did with PythonAtom, but there are differences:  

the variant always maintains its link to a Python object, and is never casted to a basic variant type (integer, string, array) when returning a value, like it was with PythonAtom.

v1 := VarPythonCreate(1);
v2 := VarPythonCreate(2);    

v1 + v2 will return a new variant that will hold the a Python object that is the result of the addition, and the addition will be performed by Python itself, using the PyNumber_Add API.     

Previously, with PythonAtom, if you accessed a property or called a method, the value returned by Python was automatically converted to a basic variant type, or wrapped up into an PythonAtom variant otherwise.     

The main advantage of this solution was to convert Python sequences (lists or tuples) into array of variants, letting us access them using the index operator (list[x]).

Now, it is not possible anymore, as the returned sequence will be returned as a new custom variant, and our custom variant doesn't support indexing!!!  I don't know how to do it? Maybe adding the flag varArray to our VType and setting array bounds that let us access any item, but I'm not sure... Anyway, it could work with sequences, but not with dictionaries, as it
accepts any type as a key.

So, to work around this problem I added several special methods/properties:

foo.GetItem(index): same as foo[index]
foo.SetItem(index, value): same as foo[index] = value
foo.Length or foo.Length(): same as len(foo)
foo.GetSlice(index1, index2): same as foo[index1:index2]
foo.SetSlice(index1, index2, value): same as foo[index1:index2] = value
foo.Contains(value): same as value in foo
foo.DeleteItem(Index): same del foo[index]          

Note that my special methods are not case sensitive.     

Note also that you must use the parenthesis with functions or methods that have no argument, to distinguish your call with a property access:

list.sort()    

if you write list.sort only, you'll get the instance method object instead of performing the sort action.     

The advantage of the new solution is that you always work with the Python objects, and you can do anything you want with them, like you would in Python. The arithmetic operations, or comparisons, work the same, and you can't add an integer to a string, for instance, as you can with variants.

v1 := VarPythonCreate(1);
v2 := v1 + 'hello'; --> Python exception    

but

v2 := 'hello' + v1;     

will work as the string is a variant that forces the right operand to be casted to a string variant, letting the concatenation to apply.      But

v2 := VarPythonCreate('hello') + v1; --> Python exception  

And

v2 := v1 + 'hello';     

--> Python exception as hello is converted to a Python variant and then an addition is beformed between a number and a
string, and Python does not like it!     

You can write also:

v1 := VarPythonCreate([1, 2, 3]);
v2 := v1 + VarPythonCreate([4, 5, 6]); --> you'll get: [1, 2, 3, 4, 5, 6]          

There are several facility functions that let you test the different types of Python objects, or that let you import other Python modules, or access the None object.

Now, you can easily execute a Python script and then access the global vars of the main module:

GetPythonEngine.ExecString('x = 2');
ShowMessage( MainModule.x ); // will display 2          

you can access the sys module with the function SysModule, or you can import any module with the Import function, that returns the imported module object.

myModule := Import('myModule');
myModule.foo(bar);          

You can create a Python variant with:

v := VarPythonCreate(myPythonObjectPointer);
v := VarPythonCreate(1);
v := VarPythonCreate('abc');
v := VarPythonCreate(3.14);
v := VarPythonCreate(True);
v := VarPythonCreate(VarArrayOf([1, 2, 3]));
v := VarPythonCreate([1, 2, 3]);
v := VarPythonCreate([1, 2, 3], stTuple);
v := None;
v := NewPythonList;
v := NewPythonList(3);
v := NewPythonDict;          

you can access to the Python object pointer stored in the variant with

ptr := ExtractPythonObjectFrom(v);          

you can test the variant with:

VarIsPython(v)
VarIsSequence(v)
VarIsMapping(v)
VarIsNumber(v)
...          

you can check if 2 variants shares the same Python object with

VarIsSame(v1, v2)          

you can check if an instance has a class that is or inherits from a class with:

VarIsInstanceOf(AInstance, AClass)          

or you can check the inheritence between 2 classes with

VarIsSubclassOf(ADerived, AClass)          

Note that these 2 functions requires Python 2 or later.     

You can check None with:

  VarIsNone(v)
or
  v = None
or
  VarIsSame(v, None)         

You can instanciate a class like you would in Python:

v := MainModule.MyClass()          

Note, that your class object must the property of a container and can't be stored in a variant:

cl := MainModule.MyClass; // cl refers to MyClass
v := cl(); // won't work because Delphi refuses to compile it!         

You can cast a Python variant to another type any time simply by assigning it to a variable of the required type or by forcing the cast with:

if Boolean(list.Contains(1)) then ...          

So, I hope these explanations will help you understand better the new approach.       Feel free to give your comments...      

Morgan

Demo25

Here are some choice extracts from demo25 (from the Demo25 folder that you get when you download the Python for Delphi components), which show how to use this stuff:

Playing with lists and dictionaries

procedure TMain.btnTestSequencesClick(Sender: TObject);
var
  a, b, c : Variant;
begin
  // initialize the operands
  // you can either use the overloaded function with an array of const
  // or use the VarArrayOf function that returns an array of variants that will
  // be casted to a Python list.
  a := VarPythonCreate([1, 2, 3]);
  Assert(VarIsPython(a));
  Assert(VarIsPythonSequence(a));
  Assert(VarIsPythonList(a));
  Assert(a.Length = 3); // this is a special property that does the same as: len(a) in Python
  Assert(a.Length() = 3); // this is a special method that does the same as the special property
  Assert(len(a) = 3);
  Assert(a.GetItem(0) = 1); // this is a special method that lets you do the same as: a[0] in Python
  Assert(a.GetItem(1) = 2);
  Assert(a.GetItem(2) = 3);
  Assert(String(a) = '[1, 2, 3]');          
  b := VarPythonCreate(VarArrayOf([4, 5, 6]));
  Assert(VarIsPython(b));
  Assert(VarIsPythonSequence(b));
  Assert(VarIsPythonList(b));
  Assert(b.Length = 3);
  Assert(b.Length() = 3);
  Assert(len(b) = 3);
  Assert(b.GetItem(0) = 4);
  Assert(b.GetItem(1) = 5);
  Assert(b.GetItem(2) = 6);
  Assert(String(b) = '[4, 5, 6]');          
  // concatenation
  c := a + b;
  // check result of operation
  Assert(String(c) = '[1, 2, 3, 4, 5, 6]');
  // check that operation did not change the content of operands.
  Assert(String(a) = '[1, 2, 3]');
  Assert(String(b) = '[4, 5, 6]');
  // now with a litteral: note that with D6 SP1, we can't 
  // concatenate a custom variant with an var array of variants
  c := a + b + VarPythonCreate(['Hello', 'World!', 3.14]);
  Assert( String(c) = '[1, 2, 3, 4, 5, 6, ''Hello'', ''World!'', 3.1400000000000001]' );
  c := a + VarPythonCreate(['Hello', 'World!', 3.14]) + b;
  Assert( String(c) = '[1, 2, 3, ''Hello'', ''World!'', 3.1400000000000001, 4, 5, 6]' );
  c := VarPythonCreate(['Hello', 'World!', 3.14]) + a + b;
  Assert( String(c) = '[''Hello'', ''World!'', 3.1400000000000001, 1, 2, 3, 4, 5, 6]' );  
  // multiplication
  c := a * 3; // in Python the multiplication of sequence concatenates n times the sequence
  Assert( String(c) = '[1, 2, 3, 1, 2, 3, 1, 2, 3]' );          
  // comparisons
  //------------          
  // equal
  c := a = b;
  Assert(c = False);
  c := a = a;
  Assert(c = True);
  Assert( String(a) = '[1, 2, 3]');          
  // not equal
  c := a <> b;
  Assert(c = True);
  Assert( not (c = b) );
  c := a <> a;
  Assert(c = False);
  Assert( String(a) = '[1, 2, 3]');          
  // greater than
  c := a > b; Assert(c = False);
  c := b > a; Assert(c = True);
  Assert( String(a) > '[1, 1, 1]');          
  // greater or equal than
  c := a >= b; Assert(c = False);
  c := b >= a; Assert(c = True);
  c := a >= a; Assert(c = True);
  Assert( String(a) >= '[1, 2, 3]' );          
  // less than
  c := a < b; Assert(c = True);
  c := b < a; Assert(c = False);
  Assert( String(a) < '[4, 4, 4]');          
  // less or equal than
  c := a <= b; Assert(c = True);
  c := b <= a; Assert(c = False);
  c := a <= a; Assert(c = True);
  Assert( String(a) <= '[1, 2, 3]');          
  // copy
  c := a;
  Assert( c = a);
  Assert( VarIsSame(c, a) ); // checks if 2 variants share the same Python object.  
  // sequence methods:
  c := b + a;
  c.sort(); // note that you must you the parenthesis to distinguish the 
		// call between a method or a property.
  Assert( c = (a+b) );          
  c := NewPythonList; // facility for building sequences
  Assert( not VarIsTrue(c) ); // c is false because it's an empty collection
  c.append(1);
  c.append(2);
  c.append(3);
  Assert( VarIsTrue(c) ); // c is true because it's not an empty collection
  Assert(c = a);
  Assert( c.pop() = 3 );
  Assert( String(c) = '[1, 2]');          
  c := NewPythonList(3); // facility for building sequences
  c.SetItem(0, 1);
  c.SetItem(1, 2);
  c.SetItem(2, 3);
  Assert(c = a);
  c.DeleteItem(1);
  Assert(c = VarPythonCreate([1,3]));          
  Assert(VarPythonCreate([1,2,3,4]).GetSlice(1, 3) = VarPythonCreate([2,3])); 
						// same as x = [1,2,3,4]; x[1:3]
  Assert(VarPythonCreate([1,2,3,4]).GetSlice(1, Ellipsis) = VarPythonCreate([2,3,4])); 
						// same as x = [1,2,3,4]; x[1:]
  Assert(VarPythonCreate([1,2,3,4]).GetSlice(1, -1) = VarPythonCreate([2,3])); 
						// same as x = [1,2,3,4]; x[1:-1]
  c := VarPythonCreate([1,2,3,4]);
  c.SetSlice(1, 3, VarPythonCreate([7, 8, 9]));
  Assert( c = VarPythonCreate([1, 7, 8, 9, 4]) );
  Assert( Boolean(c.Contains( 7 )) ); // same as 7 in c
  Assert( not Boolean(c.Contains( 77 )) );
  c.DelSlice(1,3);
  Assert( c = VarPythonCreate([1,9,4]) );          
  c := VarPythonCreate([1, 2, 3, 4], stTuple); // test a tuple
  Assert( VarIsPythonTuple(c) );
  Assert( VarIsPythonSequence(c) );
  Assert( c.GetItem(1) = 2 );
  Assert( c.Length = 4 );
  c := NewPythonTuple(3);
  c.SetItem(0, 1);
  c.SetItem(1, 2);
  c.SetItem(2, 3);
  Assert( VarIsPythonTuple(c) );
  Assert( VarIsPythonSequence(c) );
  Assert( c.GetItem(1) = 2 );
  Assert( c.Length = 3 );
  // Done!
  Log('Sequence test was Ok.');
end;          
procedure TMain.btnTestMappingsClick(Sender: TObject);
var
  a, b, c, keys, values : Variant;
begin
  // initialize the operands
  a := NewPythonDict;
  Assert(VarIsPython(a));
  Assert(VarIsPythonMapping(a));
  Assert(VarIsPythonDict(a));
  a.SetItem( 'a', 1 );
  a.SetItem( 'b', 2 );
  a.SetItem( 'c', 3 );
  Assert(a.Length = 3); 
	// this is a special property that does the same as: len(a) in Python
  Assert(a.Length() = 3); 
	// this is a special method that does the same as the special property
  Assert(len(a) = 3);
  Assert(a.GetItem('a') = 1); 
      // this is a special method that lets you do the same as: a[0] in Python
  Assert(a.GetItem('b') = 2);
  Assert(a.GetItem('c') = 3);          
  b := NewPythonDict;
  Assert(VarIsPython(b));
  Assert(VarIsPythonMapping(b));
  Assert(VarIsPythonDict(b));
  b.SetItem( 'd', 4 );
  b.SetItem( 'e', 5 );
  b.SetItem( 'f', 6 );
  Assert(b.Length = 3);
  Assert(b.Length() = 3);
  Assert(len(b) = 3);
  Assert(b.GetItem('d') = 4);
  Assert(b.GetItem('e') = 5);
  Assert(b.GetItem('f') = 6);          
  // copy
  c := a;
  Assert( c = a);
  Assert( VarIsSame(c, a) ); // checks if 2 variants share the same Python object.  
  // dict methods
  Assert( Boolean(a.has_key('a')) );
  Assert( not Boolean(a.has_key('abc')) );
  keys := a.keys();
  keys.sort();
  Assert( keys = VarPythonCreate(VarArrayOf(['a', 'b', 'c'])));
  values := a.values();
  values.sort();
  Assert( values = VarPythonCreate(VarArrayOf([1, 2, 3])));
  c := a;
  c.DeleteItem('a');
  Assert( not Boolean(c.has_key('a')) );          
  // Done!
  Log('Mapping test was Ok.');
end;          

playing with objects

procedure TMain.btnTestObjectsClick(Sender: TObject);
var
  _main, f : Variant;
  val : Integer;
  _folder, _str : String;
  _myModule : Variant;
begin
  PythonEngine1.ExecStrings(Memo2.Lines);
  _main := MainModule;
  Assert( VarIsPythonModule(_main) );
  Assert( VarIsPythonModule(SysModule) );
  Assert( Import('sys').version = SysModule.version );
  Assert( Boolean(SysModule.modules.has_key(GetPythonEngine.ExecModule)) ); 
					// if __main__ in sys.modules
  Assert( VarIsSameType(_main, SysModule) );
  Assert( _type(_main).__name__ = 'module');
  Assert( BuiltinModule.type(_main).__name__ = 'module');

  Assert( VarIsPythonClass(_main.Foo) );
  Assert( VarIsPythonCallable(_main.Foo) );
  Assert( VarIsPythonCallable(_main.Foo) );
  Assert( VarIsTrue(BuiltinModule.callable(_main.Foo)) );
  Assert( VarIsPythonInstance(_main.f) );
  Assert( VarIsSame(_main.f.__class__, _main.Foo) );
  Assert( VarIsPythonMethod(_main.f.Inc) );
  Assert( VarIsPythonCallable(_main.f.Inc) );
  Assert( VarIsTrue(BuiltinModule.callable(_main.f.Inc)) );
  Assert( VarIsPythonFunction(_main.Add) );
  Assert( VarIsPythonCallable(_main.Add) );
  Assert( VarIsInstanceOf(_main.f, _main.Foo) );
  Assert( VarIsTrue(BuiltinModule.isinstance(_main.f, _main.Foo)) );
  Assert( VarIsSubclassOf(_main.Bar, _main.Foo) );
  Assert( VarIsTrue(BuiltinModule.issubclass(_main.Bar, _main.Foo)) );
  Assert( not VarIsSubclassOf(_main.Foo, _main.Bar) );
  Assert( VarIsInstanceOf(_main.b, _main.Foo) );
  Assert( not VarIsInstanceOf(_main.f, _main.Bar) );

  Assert( VarIsTrue( BuiltinModule.vars(_main).has_key('f') ) );
  Assert( VarIsTrue( BuiltinModule.dir(_main).Contains('f') ) );

  f := _main.Foo(); // new instance of class Foo
  Log('Instanciate class Foo: ' + f);
  f.Inc(); // call a method without any arg, because there's a default arg.
  f.Inc(2); // call a method with one arg, overriding the default arg.
  Assert( VarIsPythonNumber(f.Value) );
  Assert( VarIsPythonInteger(f.Value) );
  Assert( f.Value = _main.f.Value ); // compare the result with what we did in the script
  Assert( f.GetValue() = _main.f.GetValue() ); 
			// compare the result with what we did in the script
  Assert( VarIsPython( f.GetSelf() ) );
  Assert( VarIsSame( f.GetSelf(), f ) );
  Assert( BuiltinModule.getattr(f, 'Value') = f.Value );
  // cascading calls
  Assert( f.GetSelf().GetSelf().GetSelf().GetSelf().GetValue() = _main.f.GetValue() );
  Assert( Boolean(f.__dict__.has_key('Value')) );
  Assert( VarIsTrue( BuiltinModule.hasattr(f, 'Value') ) );
  _str := 'Value';
  Assert( Boolean(f.__dict__.has_key(_str)) ); // check with a string var
  Assert( Boolean( BuiltinModule.hasattr(f, _str) ) );
  val := f.Value;
  f.Add(f); // passing itself as an argument
  Assert( f.Value = val*2 );
  // check param order
  f.SetABC(1, 2, 3);
  Assert(f.A = 1);
  Assert(f.B = 2);
  Assert(f.C = 3);
  // add a property to an instance
  f.Z := 99;
  Assert(f.Z = 99);
  // add a var to a module
  _main.Z := 99;
  Assert(_main.Z = 99);
  // check none
  Assert( VarIsNone(None) );
  Assert( VarIsNone(VarPythonCreate([1, Null, 3]).GetItem(1)) ); // Null is casted to None
  Assert( VarIsNone(VarPythonCreate([1, None, 3]).GetItem(1)) );
  Assert( VarIsNone(f.Inc()) );
  Assert( f.Inc() = None );
  Assert( not Boolean(None) ); // if not None:
  Assert( not VarIsTrue(None) ); // if not None:
  Assert( Boolean(f) ); // if f:
  Assert( VarIsTrue(f) ); // if f:

  // call a function
  Assert( _main.Add(2, 2) = 4 );
  // importing an external module and using it
  // first, extend the path with our current folder
  _folder := ExtractFilePath(Application.ExeName);
  if (Length(_folder) > 0) and (_folder[Length(_folder)] = '\') then
    Delete(_folder, Length(_folder), 1);
  if not Boolean(SysModule.path.Contains(_folder)) then
    SysModule.path.insert(0, _folder);
  // import the module
  _myModule := Import('MyModule');
  // call one of his functions
  Assert( _myModule.Add(2, 2) = 4 );
  // delete module var f
  _main.__dict__.DeleteItem('f');
  Assert( _main.__dict__.has_key('f') = False );
  // open a file using Python
  if FileExists('MyModule.py') then
  begin
    f := BuiltinModule.open('MyModule.py', 'r').readlines();
    with TStringList.Create do
    try
      LoadFromFile('MyModule.py');
      Assert( len(f) = Count);
    finally
      Free; // TStringList
    end; // of try
  end; // of if
end;    


END OF DEMO25 listing - note this is copied from the Demo25 folder that you get when you download the Python for Delphi components.

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

accessing delphi-form by pythonscript inside delphi-application
2011-03-18 16:15
accessing delphi-form by pythonscript inside delphi-application
 
VarPyth.pas
In the hurry of announcing the beta, I forgot to give some hints on the new
VarPyth.pas:

It wraps a Python object and lets you act on it (call methods or use
properties) like you would do in Python, and like you did with PythonAtom,
but there are differences:
the variant always maintains its link to a Python object, and is never
casted to a basic variant type (integer, string, array) when returning a
value, like it was with PythonAtom.
v1 := VarPythonCreate(1);
v2 := VarPythonCreate(2);
v1 + v2 will return a new variant that will hold the a Python object that is
the result of the addition, and the addition will be performed by Python
itself, using the PyNumber_Add API.

Previously, with PythonAtom, if you accessed a property or called a method,
the value returned by Python was automatically converted to a basic variant
type, or wrapped up into an PythonAtom variant otherwise.

The main advantage of this solution was to convert Python sequences (lists
or tuples) into array of variants, letting us access them using the index
operator (list[x]).
Now, it is not possible anymore, as the returned sequence will be returned
as a new custom variant, and our custom variant doesn't support indexing!!!
I don't know how to do it? Maybe adding the flag varArray to our VType and
setting array bounds that let us access any item, but I'm not sure...
Anyway, it could work with sequences, but not with dictionaries, as it
accepts any type as a key.
So, to work around this problem I added several special methods/properties:
foo.GetItem(index): same as foo[index]
foo.SetItem(index, value): same as foo[index] = value
foo.Length or foo.Length(): same as len(foo)
foo.GetSlice(index1, index2): same as foo[index1:index2]
foo.SetSlice(index1, index2, value): same as foo[index1:index2] = value
foo.Contains(value): same as value in foo
foo.DeleteItem(Index): same del foo[index]

Note that my special methods are not case sensitive.

Note also that you must use the parenthesis with functions or methods that
have no argument, to distinguish your call with a property access:
list.sort()
if you write "list.sort" only, you'll get the instance method object instead
of performing the sort action.

The advantage of the new solution is that you always work with the Python
objects, and you can do anything you want with them, like you would in
Python. The arithmetic operations, or comparisons, work the same, and you
can't add an integer to a string, for instance, as you can with variants.
v1 := VarPythonCreate(1);
v2 := v1 + 'hello'; --> Python exception
but v2 := 'hello' + v1; will work as the string is a variant that forces the
right operand to be casted to a string variant, letting the concatenation to
apply.

But v2 := VarPythonCreate('hello') + v1; --> Python exception
And v2 := v1 + 'hello'; --> Python exception as hello is converted to a
Python variant and then an addition is beformed between a number and a
string, and Python does not like it!

You can write also:
v1 := VarPythonCreate([1, 2, 3]);
v2 := v1 + VarPythonCreate([4, 5, 6]); --> you'll get: [1, 2, 3, 4, 5, 6]

There are several facility functions that let you test the different types
of Python objects, or that let you import other Python modules, or access
the None object.
Now, you can easily execute a Python script and then access the global vars
of the main module:
GetPythonEngine.ExecString('x = 2');
ShowMessage( MainModule.x ); // will display 2

you can access the sys module with the function SysModule, or you can import
any module with the Import function, that returns the imported module
object.
myModule := Import('myModule');
myModule.foo(bar);

You can create a Python variant with:
v := VarPythonCreate(myPythonObjectPointer);
v := VarPythonCreate(1);
v := VarPythonCreate('abc');
v := VarPythonCreate(3.14);
v := VarPythonCreate(True);
v := VarPythonCreate(VarArrayOf([1, 2, 3]));
v := VarPythonCreate([1, 2, 3]);
v := VarPythonCreate([1, 2, 3], stTuple);
v := None;
v := NewPythonList;
v := NewPythonList(3);
v := NewPythonDict;

you can access to the Python object pointer stored in the variant with
ptr := ExtractPythonObjectFrom(v);

you can test the variant with:
VarIsPython(v)
VarIsSequence(v)
VarIsMapping(v)
VarIsNumber(v)
...

you can check if 2 variants shares the same Python object with
VarIsSame(v1, v2)

you can check if an instance has a class that is or inherits from a class
with:
VarIsInstanceOf(AInstance, AClass)

or you can check the inheritence between 2 classes withL
VarIsSubclassOf(ADerived, AClass)

Note that these 2 functions requires Python 2 or later.

You can check None with:
VarIsNone(v)
or
v = None
or
VarIsSame(v, None)


You can instanciate a class like you would in Python:
v := MainModule.MyClass()

Note, that your class object must the property of a container and can't be
stored in a variant:
cl := MainModule.MyClass; // cl refers to MyClass
v := cl(); // won't work because Delphi refuses to compile it!


You can cast a Python variant to another type any time simply by assigning
it to a variable of the required type or by forcing the cast with:
if Boolean(list.Contains(1)) then ...


So, I hope these explanations will help you understand better the new
approach.

Feel free to give your comments...

Morgan


 

 

Example1
Home Up

 

Using Delphi and Python together - An Example

A Delphi app which has all the business logic implemented as Python code.  A perfect marriage of two worlds!

The Delphi Code

Tip: The bold entries in the code below are references to python objects.
unit mainFrm;
interface
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  PythonEngine, AtomPythonEngine, StdCtrls, ExtCtrls, PythonGUIInputOutput,
  uHeartMachine;
type
  TfrmMain = class(TForm)
    PE: TAtomPythonEngine;
    PythonInputOutput1: TPythonInputOutput;
    pdvUser: TPythonDelphiVar;
    pdvShare: TPythonDelphiVar;
    pdvOrderManager: TPythonDelphiVar;
    pdvBankBalance: TPythonDelphiVar;
    pdvNumShares: TPythonDelphiVar;
    pdvSharePrice: TPythonDelphiVar;
    PythonDelphiVar2: TPythonDelphiVar;
    lblSharePrice: TLabel;
    bntBuy: TButton;
    btnForceStockToDrop: TButton;
    Timer1: TTimer;
    lblBankBalance: TLabel;
    lblNumShares: TLabel;
    btnSell: TButton;
    Memo1: TMemo;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    lblSharesAreWorth: TLabel;
    Label5: TLabel;
    PythonGUIInputOutput1: TPythonGUIInputOutput;
    btnForceStockToRise: TButton;
    cbNumShares: TComboBox;
    Label6: TLabel;
    cbUserLimit: TComboBox;
    Label7: TLabel;
    lblTotalWorth: TLabel;
    Label8: TLabel;
    cbNumAttempts: TComboBox;
    Label9: TLabel;
    Panel1: TPanel;
    HeartMachine1: THeartMachine;
    Panel2: TPanel;
    HeartMachine2: THeartMachine;
    Panel3: TPanel;
    HeartMachine3: THeartMachine;
    procedure PythonInputOutput1SendData(Sender: TObject;
      const Data: String);
    procedure pdvSharePriceSetData(Sender: TObject; Data: Variant);
    procedure FormShow(Sender: TObject);
    procedure bntBuyClick(Sender: TObject);
    procedure btnForceStockToDropClick(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure btnSellClick(Sender: TObject);
    procedure btnForceStockToRiseClick(Sender: TObject);
    procedure cbUserLimitChange(Sender: TObject);
    procedure cbNumAttemptsChange(Sender: TObject);
    procedure PEPathInitialization(Sender: TObject; var Path: String);
  private
    { Private declarations }
  public
    { Public declarations }
  end;
  TmgmStockMarketMainForm = class(TObject) // ;  // Transformation Interface
  private
    LastPrice : integer;
  public
    procedure display;
    procedure repopulateFromGui;
  end;
var
  frmMain: TfrmMain;
implementation
uses AndyDelphiPy, ComCtrls, pythonAtom, Math;
var
  Share, User, OrderManager : OleVariant;
  mgmMainForm : TmgmStockMarketMainForm;
{$R *.DFM}
procedure TfrmMain.PythonInputOutput1SendData(Sender: TObject;
  const Data: String);
begin
  PyConsoleOut(Data);
end;
procedure TfrmMain.pdvSharePriceSetData(Sender: TObject; Data: Variant);
begin
  lblSharePrice.Caption := Data;
end;
procedure TfrmMain.FormShow(Sender: TObject);
begin
  //PyExeFile('E:\Python20\Boa-0.0.5\AndysTry\Questtest.py', PE);
  PyExeFile('QuesttestFROZEN.py', PE);
  User := PyClass('User()', pdvUser, PE);
  Share := PyClass('Share("Friendly Co.", worth=10)', pdvShare, PE);
  OrderManager := PyClass('OrderManager()', pdvOrderManager, PE);
  mgmMainForm := TmgmStockMarketMainForm.Create;
  timer1.enabled := true;
end;
procedure TfrmMain.bntBuyClick(Sender: TObject);
begin
  OrderManager.AddOrder(User.myself, strtoint(cbNumShares.text), 'buy', Share.myself);
end;
procedure TfrmMain.btnSellClick(Sender: TObject);
begin
  OrderManager.AddOrder(User.myself, strtoint(cbNumShares.text), 'sell', Share.myself);
end;
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
  try
    Share.fluxtuate;
    OrderManager.ProcessOrders;
    mgmMainForm.display;
  except
    timer1.enabled := false;
    showmessage('Some error cancelled the game.');
  end;
end;
{ TmgmStockMarketMainForm }
procedure TmgmStockMarketMainForm.display;
var
  f : double;
begin
  if LastPrice = Share.CurrentPrice then
    with frmMain.lblSharePrice do
      Caption := Caption + '.'
  else
    frmMain.lblSharePrice.Caption := '$ ' + inttostr(Share.CurrentPrice);
  LastPrice := Share.CurrentPrice;
  f := User.BankBalance;
  frmMain.lblBankBalance.Caption := format('%m', [ f ]);
  frmMain.lblNumShares.Caption := User.NumSharesOwned;
  f := User.NumSharesOwned * Share.CurrentPrice;
  frmMain.lblSharesAreWorth.Caption := format('%m', [ f ]);
  f := f + User.BankBalance;
  frmMain.lblTotalWorth.Caption := format('%m', [ f ]);
  frmMain.cbNumAttempts.Text := OrderManager.MaxAttempts;
  frmMain.cbUserLimit.Text := User.SharePriceLimit;
  frmMain.HeartMachine1.AddPoint(0, min(Share.CurrentPrice, 100), 'Share Price');
  frmMain.HeartMachine2.AddPoint(0, min(User.BankBalance, 50000), 'Bank Balance');
  frmMain.HeartMachine3.AddPoint(0, min(User.NumSharesOwned, 1000), 'Num Shares Owned');
end;
procedure TmgmStockMarketMainForm.repopulateFromGui;
begin
  User.SharePriceLimit := strtoint( frmMain.cbUserLimit.Text );
  OrderManager.MaxAttempts := strtoint( frmMain.cbNumAttempts.Text );
  display;
end;
procedure TfrmMain.cbUserLimitChange(Sender: TObject);
begin
  mgmMainForm.repopulateFromGui;
end;
procedure TfrmMain.cbNumAttemptsChange(Sender: TObject);
begin
  mgmMainForm.repopulateFromGui;
end;
procedure TfrmMain.btnForceStockToRiseClick(Sender: TObject);
begin
  Share.CurrentPrice := Share.CurrentPrice + 10 + random(20);
end;
procedure TfrmMain.btnForceStockToDropClick(Sender: TObject);
begin
  Share.CurrentPrice := random(10);
end;


procedure TfrmMain.PEPathInitialization(Sender: TObject; var Path: String);
begin
  //path := 'e:\try\deploy;e:\try\deploy\lib';
end;
end.
Tip: The bold entries in the code above are references to python objects.

The HeartMachine Delphi component is available from here.

The Python Code

Note:  Not all the python code is shown, due to confidentiality issues.

# Share trading game simulator.
false = 0 ; true = 1
import random, sys
def log(s):
    print s
    #sys.stdout.flush()
class Share:
    def __init__(self, name, worth=10):
        self.CurrentPrice = worth
        self.Name = name
    def __str__(self):
        return 'Share ' + self.Name + '  Worth= $' + `self.CurrentPrice` + ' per share.'
    def fluxtuate(self):
        if random.randrange(0,2):
            self.CurrentPrice = self.CurrentPrice + random.randrange(-10,10)
        if self.CurrentPrice <= 0:
            self.CurrentPrice = 1
        ##log('..time passes.. ' + str(self))
    def myself(self):
        return self
class User:
    def __init__(self, initialBankBalance=1000, initialNumShares=0, initialSharePriceLimit=10):
        self.BankBalance = initialBankBalance
        self.NumSharesOwned = initialNumShares
        self.SharePriceLimit = initialSharePriceLimit
    def __str__(self):
        return 'User Bank balance='+`self.BankBalance` + \
               ' Number of Shares=' + `self.NumSharesOwned` + \
               ' Share Price Limit=' + `self.SharePriceLimit`
    def myself(self):
        return self
class Order:
    def candivest(self):
        return self.NumberOfShares <= self.User.NumSharesOwned
    def canafford(self):
        return (self.Share.CurrentPrice*self.NumberOfShares) < self.User.BankBalance
    def sharepricetoolow(self):
        return self.Share.CurrentPrice < self.User.SharePriceLimit
    def sharepricetoohigh(self):
        return self.Share.CurrentPrice > self.User.SharePriceLimit
    def __init__(self, user, numshares, buyorsell, share):
        self.User = user
        self.Share = share
        self.NumberOfShares = numshares
        self.Command = buyorsell
        #print 'DEBUG - self.Share.CurrentPrice', self.Share.CurrentPrice # debug !!!!
    def __str__(self):
        return 'Order to %s %d %s shares' % (self.Command, self.NumberOfShares, self.Share.Name)

etc.

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

 

Deployment Issues
Home Up

 

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

Deploying Python for Delphi applications

When deploying delphi application that uses the python for delphi components, you need to supply python21.dll  (or whatever version you are working with) plus any .py files.  Simple advice, but the devil is in the detail - as I found out!! 

Nevertheless, I managed to successfully deploy a delphi app to hundreds of users and they didn't even know there was python inside !

-Andy Bulka
February 2002.

Quick tips

Tips:

  1. Supply a version of python2x.dll with your delphi exe.  Put it in the same folder or in the windows system folder.
  2. Supply any python classes/module files (.py or .pyc) plus any dlls (.pyd) that is used.
  3. Ensure your python path is set to point to your .py files.  If using Delphi 6 & Python 21 or higher, you can use the advanced technique below of using a special API call provided by the python for delphi components.  If using earlier versions of Delphi (5 and below) and Python (2.0 and below) then use the simpler more direct technique of talking to the python interpreter via text script.
  4. Beware that the DOS environment variable PYTHONPATH may intefere with your path, if it points to a different version of the python libraries than the one you supply with your exe. Similarly, an existing registry entry for python may also intefere with your desired path.  For a discussion on this click here.
  5. You may need to set the lastKnownDll boolean property of your python engine component to false, so that the python for delphi components actively search for the dll specified in the UseDLL property of your python engine component, rather than using the last successfully found python dll. 
    UPDATE: Apparently in more recent versions of the python for delphi components you don't need to set it since it will load the version you have compiled for.
  6. When compiling for specific versions of the python dll, you need to create a define in the delphi project options.  Here is a viewlet demo of this technique that you can watch.  Here is a discussion on this issue.

Does the python for delphi demo25 rely on python 2.1?

Andy: It seems that  Demo25 does indeed rely on both delphi 6 and python 2.1

Morgan:  Demo25 relies on Python2.1 indirectly, as it calls the function VarIsInstanceOf, which relies on version 2.1, but if you comment out this call (and VarIsSubclassOf), it should work without any problem!

Andy: It seems that to use, say python21.dll on a system that only has the python2.0 development environment installed, one need not fully install python 2.1, all you need to do is put python21.dll into winnt\system32 or  into the folder you are developing the delphi application in.

Morgan: Yes, that's right!

Andy:  You can put the Lib folder anywhere you like, and have Delphi point to it e.g. SysModule.path.append('..blah..\Lib'). 

Morgan:  That's a solution, but the Python Dll gets its path from the Windows registry:

HKEY_LOCAL_MACHINE\Software\Python\PythonCore\2.1\PythonPath        

Of course, each version of Python has its own entry.

Andys Quick Tip

When you supply your python classes with your delphi app, they will probably use some standard python classes.  You will need to supply these .py files too.  Either ship/supply the whole Lib folder or supply just what actually has been used.  How to determine this?  I used trial and error, - leveraging Py2Exe style technology would be a better solution, so that a tool identifies for you the dependant files so you know which to ship.

NOTE: If there are any dependant .pyd files (actually DLL's) then these need to be shipped too.

NOTE:  When supplying the python classes with your delphi exe, you can put the Lib folder anywhere you like, and point to it using the python list variant SysModule.path (accessible and modifiable from Delphi).

The current folder of the Delphi EXE is automatically added to the SysModule.path (by PythonEngine, I think), so you can put Strings.py or whatever in the current folder of your Delphi application, if you like, though its probably better to follow the python standards and keep this stuff in a Lib folder.

The 'DllName' property of the pythonEngine

Andy:  Changing the DllName property of the pythonEngine doesn't seem to affect things - even if I put python15.dll in the demo25 example, things still run ok! So how do I control / verify which python dll is being used and accessed? What is the DllName property really doing, if anything?

Morgan:  You forgot to disable the UseLastKnownVersion property in the TPythonEngine!!!  If it's true, it tries each version of Python, starting from the greatest version number, and uses what it finds. If nothing is found, then DllName is used!

So, if you have several versions of Python installed in your system, you can't rely on this property, and you should set it to False and define the DllName corresponding to the expected Python version, but don't forget to specify the properties APIVersion and RegVersion. If you have a doubt, look at the beginning of PythonEngine.pas, that defines all known values in an array (PYTHON_KNOWN_VERSIONS).

[ Note: You don't even need to set UseLastKnownVersion to false now, because the latest strategy is to find first the dll for which your code was compiled, and then try to upgrade if it's missing. ]

Compiling for different versions of python

Running python 2.2 from delphi 6

Andy:  I am getting an error when I start my dephi app 'Error 127: could not map symbol "PyUnicode_FromWideChar"  This is the first time I've tried running a python 2.2.1 app. I was using 2.1 ok, then to change to 2.2 I changed the AtomEngine component properties:
> UseLastKnownVersion false
> RegVersion 2.2
> DllName python22.dll

Morgan:  You don't need to do this for selecting a Python version! Keep UseLastKnownVersion to True, and adjust your defines:
{$DEFINE PYTHON22}
UseLastKnownVersion will first try to find the dll version you requested (2.2), and if this version is flagged compatible with upgrades, then it will
look for installed upgrades. That way it ensures that it will always try to get the dll for which your code was compiled with.

Andy: Ok it works now. :-) These two steps seem to be the key to getting it working and all I have to do. When I turn UseLastKnownVersion to false, I get into trouble I was having.

Furthermore, playing with
RegVersion 2.1 or 2.2
DllName python22.dll
doesn't seem to help either. Perhaps these properties should be retired as misleading? And perhaps UseLastKnownVersion should be retired and always set to true?

Andy's Quick Tip                    

To compile for python 2.2 just 

Keep 

UseLastKnownVersion to True

on your PythonEngine component and adjust your define:

{$DEFINE PYTHON22}

which you can do in the project options of Delphi 6.  
Just add PYTHON22 to the dialog box e.g.

Cleaning up my python path from within Delphi

Andy Bulka

I want to ship (with my .exe) a few python units in the 'MyLib' folder underneath where my delphi .EXE lives. The destination machine doesn't have python, so I need to supply all the relevant classes (by the way are there any dependency utils for ensuring I identify all the dependent class files?)

Anyway, so I want to set the sys.path appropriately to make sure my python class files are found. Of course I could put all the classes in the same folder as my EXE and this would solve the problem, cos I know the current folder of my EXE is automatically added to the sys.path.

But I want to keep my python classes in the 'MyLib' folder underneath where my delphi .EXE lives.

I know that when a python engine starts up inside delphi, the sys.path is set via the Windows registry:
HKEY_LOCAL_MACHINE\Software\Python\PythonCore\2.1\PythonPath

If I'm using Delphi 6 and python 2.1 then I can use the following cool delphi code to trim the path and set it to something I want.

SysModule.path := NewPythonList;
SysModule.path.append(ExtractFilePath( Application.ExeName ) + 'MyLib' );
SysModule.path.append('some other folder');        

NOTE:  Above relies on Jan 2002 version of python for Delphi components.
NOTE: Above will fail since the paths returned by Delphi have single slashes and python needs wither unix slashes or \\ slashes. See here for an algorithm to handle this.

MY QUESTION: Under delphi 4, I don't have access to the variant unit and the nice SysModule variable. Is the only alternative to send some python script text at the python interpreter to set sys.path ? e.g.

Memo1.Lines.Text := 'import sys' ;
PythonEngine1.ExecStrings( Memo1.Lines );        
apath := ExtractFilePath( Application.ExeName ) + 'MyLib' ;
Memo1.Lines.Text := 'sys.path.append("' + apath + '")' ;
PythonEngine1.ExecStrings( Memo1.Lines );        

**** above code works, ok by the way

Andy tip: Algorithm to clear out your syspath and set it to exactly what you want.  Will work with any version of delphi or python:
function EnsurePathHasDoubleSlashes( path : string ):string;
begin
	result := StringReplace(path, '\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
	result := StringReplace(result, '\\\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
end;     
// Sets the path to the app EXE path plus the 'path' underneath
// the current EXE path. Returns false if 'path' does not exist.     
function PyClearSysPathAndSetSimple(folder : string; engine: TAtomPythonEngine) : boolean;
var
	cmds, currExePathInclSlash, libPath, currPath : string;
begin
	result := false;
	currExePathInclSlash := ExtractFilePath( Application.ExeName );                    
	libPath := currExePathInclSlash + folder;
	currPath := copy(currExePathInclSlash,1,length(currExePathInclSlash)-1);                    
	if not DirectoryExists( libPath ) then begin
		showmessage('no DirectoryExists( libPath ) !!! ' + libPath);
		exit;
	end;                    
	libPath := EnsurePathHasDoubleSlashes(libPath);
	currPath := EnsurePathHasDoubleSlashes(currPath);                    
	cmds := 'import sys' + #13 +
	'while sys.path:' + #13 +
	' sys.path.pop()'+ #13 +
	'sys.path.append("' + currPath + '")' + #13 +
	'sys.path.append("' + libPath + '")' + #13 +
	'';
	PyExe(cmds,engine);

	result := true;
end;                    

Morgans' reply:

You have another solution by using Python APIs:

Here are some sys APIs:

function PySys_GetObject(s:PChar):PPyObject; cdecl;
function PySys_SetObject(s:PChar;ob:PPyObject):integer; cdecl;
procedure PySys_SetPath(path:PChar); cdecl;
procedure PySys_SetArgv( argc: Integer; argv: PPChar); cdecl;        

if you do:

PySys_SetPath('c:\mylib');

it will be the same than doing in Python:

import sys
sys.path = 'c:\\mylib';

if you want to know the path content, simply use PySys_GetObject('path') and it will return a borrowed reference to the Python path string object.  If you want to replace it, you can do:

PySys_SetObject('path',MyNewPathStringObject);        

But don't forget to decrement the reference count of MyNewPathStringObject.
In fact, if you look at the PySys_SetPath C code:

void
PySys_SetPath(char *path)
{
	PyObject *v;
	if ((v = makepathobject(path, DELIM)) == NULL)
		Py_FatalError("can't create sys.path");
	if (PySys_SetObject("path", v) != 0)
		Py_FatalError("can't assign sys.path");
	Py_DECREF(v);
}        
Andy's Quick Tip                    

When setting slashes for paths in Delphi to python, ensure you turn all \ into \\   Here is a utility that does this:

function EnsurePathHasDoubleSlashes( path : string ):string;
begin
	result := StringReplace(path, '\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
	result := StringReplace(result, '\\\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
end;                    

Compiling delphi app to use specific versions of python dll.

I've was  playing with using different versions of python from delphi, using the latest release of your components.  Here are some scenarios:

python 2.1 By default it seems that python21.dll is used OK
python 2.0 when I want to use python20.dll, I simply set the PYTHON20 define in project options, set the engine property DllName to python20.dll and set the engine property UseLastKnownVersion to false Works OK.

[ Note: You don't even need to set UseLastKnownVersion to false now, because the latest strategy is to find first the dll for which your code was compiled, and then try to upgrade if it's missing. ]

python 2.2 I assume I use the same steps as 2.0 (haven't tried this).  
python 1.52 When I want to use python15.dll, and follow the same steps, compilation fails at PythonEngine.pas at function Traverse( proc: visitproc; ptr: Pointer) : integer; virtual; since visitproc is only defined in PYTHON20_OR_HIGHER, as the following snippet reveals: Issue fixed in latest release of python for delphi components. Feb 2002.

Note: Compiling issues. Set the DEFINE inside the project you are using. setting it in the python for delphi package options does not help since whilst the package is fine, the .exe you build uses .dcu's which are recompiled on demand, using your project options.  Thus you need to set the actual project options.  here is a viewlet demo of this technique.

-Andy Bulka.

How to get python for Delphi to stop looking at the PYTHONPATH environment variable and at any existing python path in the registry

Question: Do you know any way to stop python21 from
looking at PYTHONPATH environment variable, when loaded from delphi?

If I want a truly independent distibution, then supplying my own libs and python21.dll can be thwarted by the user's machine happening to have a
PYTHONPATH defined, and pointing to an old set of python libs (e.g. python20).  The wrong os.py gets loaded etc. etc. Even if the first thing I do in a python script executed by a freshly loaded python engine is clear the sys.path, somehow the older libs found in PYTHONPATH have influenced the equation. Only by removing/renaming the PYTHONPATH environment variable do I avoid the glitches.

I tried clearing the PYTHONPATH dos environment variable in delphi, prior to creating a form on which the atomengine lived - but it seems that python is
loaded into a fresh process space with a fresh set of environment variables (including the nasty PYTHONPATH ). Note that sometimes this solution DID work e.g. on a simple sample app (thus looked like a promising solution) - but didn't work for my big serous delphi app - not sure why not, perhaps cos of the
fresh process space with a fresh set of environment variables hypothesis...

I soon intend to write an article about deploying with python for delphi, including the issues & gotchas I have encountered, so any thoughts about this
rather serious gotcha would be appreciated and shared. It may apply to registry paths too, if you want to override any existing libs and supply your
own. I noticed in python 22 there was something about a flag to ignore the environment - perhaps that is also relevant?

cheers,
Andy Bulka
www.atug.com/andypatterns

P.S.

Can you suggest how to avoid picking up pythonpath? I scanned through the components source code and you only look in the registry for PythonPath, so python itself must be looking at the dos environment.

I have set the engine dll name manually in the property editor.  I have set UseLastKnown to false, and verified the correct dll is being loaded. The python21.dll is in the same directory as the exe, and the Lib and DLLs folders underneath the folder with the exe.

Even the solution of clearing this environment variable BEFORE the form/datamodule with the python engine is even created.helps in some apps, but for others doesn't work either.

Initial reply from Morgan

> Whilst I've got your attention - do you know any way to stop python21 from looking at PYTHONPATH environment variable, when loaded from delphi?

Morgan: Not especially!

> If I want a truly independent distibution, then supplying my own libs and python21.dll can be thwarted by the user's machine happening to have a PYTHONPATH defined, and pointing to an old set of python libs (e.g. python20). The wrong os.py gets loaded etc. etc. Even if the first thing I do in a
python script executed by a freshly loaded python engine is clear the sys.path, somehow the older libs found in PYTHONPATH have influenced the equation.
Only by removing/renaming the PYTHONPATH environment variable do I avoid the glitches.

Morgan: Ok.

> I tried clearing the PYTHONPATH dos environment variable in delphi, prior to creating a form on which the atomengine lived - but it seems that python is loaded into a fresh process space with a fresh set of environment variables (including the nasty PYTHONPATH ). Note that sometimes this solution DID work e.g. on a simple sample app (thus looked like a promising solution) - but didn't work for my big serous delphi app - not sure why not, perhaps cos of the
fresh process space with a fresh set of environment variables hypothesis...

Morgan: Ok. Anyway, you have to clear the registry also.  A Dll shares the same address space of the process it runs into.

>It may apply to registry paths too, if you want to override any existing libs and supply your own. I noticed in python 22 there was something about a flag to ignore the environment - perhaps that is also relevant?

Morgan: It doesn't work with Windows, only Unix.

I looked at Python source code and found out a way:

Simply add the following line of code in the OnBeforeLoad event of
TPythonEngine:

SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');        

And it will generate the following path:

['D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'c:\\myHome\\DLLs',
'c:\\myHome\\lib', 'c:\\myHome\\lib\\plat-win', 'c:\\myHome\\lib\\lib-tk',
'D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25']        

In fact, I should even add a special event for this...

Here are some comments extracted from Python source code:

All PC ports use this scheme to try to set up a module search path:        
from PC\readme.txt        
1) The script location; the current directory without script.
2) The PYTHONPATH variable, if set.
3) For Win32 platforms (NT/95), paths specified in the Registry.
4) Default directories lib, lib/win, lib/test, lib/tkinter;
these are searched relative to the environment variable
PYTHONHOME, if set, or relative to the executable and its
ancestors, if a landmark file (Lib/string.py) is found ,
or the current directory (not useful).
5) The directory containing the executable.        
from PC\getpathp.c        
/* We need to construct a path from the following parts.
(1) the PYTHONPATH environment variable, if set;
(2) for Win32, the machinepath and userpath, if set;
(3) the PYTHONPATH config macro, with the leading "."
of each component replaced with pythonhome, if set;
(4) the directory containing the executable (argv0_path).
The length calculation calculates #3 first.
Extra rules:
- If PYTHONHOME is set (in any way) item (2) is ignored.
- If registry values are used, (3) and (4) are ignored.
*/        

Afterthought....

From: Morgan Martinet [morgan.martinet@altavista.net]

Hi again!

In my last mail I omitted a point: I had removed the registry entries of Python, and it worked fine. But have proper registry settings override the PYTHONHOME path, as they are placed first in the path list.

Anyway, I discovered that you can define your own Python settings in the current user registry, and they will override the default settings of local machine!

So, with regedit I defined the following entries:

Windows Registry Editor Version 5.00        
[HKEY_CURRENT_USER\Software\Python]
[HKEY_CURRENT_USER\Software\Python\PythonCore]
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1]
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath] @="d:\\default" 
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\PythonWin] @="d:\\pythonwin"
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\Win32] @="d:\\win32com"
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\win32com]        

And I get the following path from Python:

['D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'd:\\pythonwin', 'd:\\win32com', 'D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'd:\\default', 'C:\\Python21\\Pythonwin', 'C:\\Python21\\win32', 'C:\\Python21\\win32\\lib', 'C:\\Python21', 'C:\\Python21\\Lib\\plat-win', 'C:\\Python21\\Lib', 'C:\\Python21\\DLLs', 'C:\\Python21\\Lib\\lib-tk']

Hope this helps,

Andy still is not clear:

So you are saying that inserting d:\\default into PythonPath is also needed? in conjunction with SetEnvironmentVariable('PYTHONHOME', 'd:\default');

So in summary, the combination:

SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');        

and

[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath]@="d:\\default" 
        

( the latter hopefully should appear before any other refs to any other \lib.)

will fix the problem?

I'll play around some more with the new PYTHONHOME idea. But try point your PYTHONPATH to a python20 lib folder and load python21.dll via delphi, and load os and urllib etc. Weird things happen unless the correct versions are loaded, that's for sure. I'll see how the PYTHONHOME works...

Morgan's final word on this issue:

You don't even need to use: SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');

Simply override the Python registry settings for the current user, as you can't do it for the local machine if the logged user has not admin rights.

Andy's final Solution:

I never did resolve this.  So I just added some code to the delphi app which checked to see if PYTHONPATH was defined.  If it was, the app refuses to run.  I just couldn't afford to take chances and didn't have the time to resolve this issue to my satisfaction.

Anybody else is welcome to email me so I can do a final summary.

Where does python for delphi look for the DLL at run time?

>Is there any way I can control WHERE it looks for the DLL at run time?
>or, failing that, at compile time?

Yes, simply set the property UseLastKnownVersion to False, and set the property DllName to the fully qualified path of your python21.dll. Don't forget to set the properties APIVersion to 1010 and RegVersion to 2.1

If you want to do it by code, you must also set the property AutoLoad to False, and set the previous properties in the OnCreate event of your form or datamodule, and finally call the method LoadDll.

Note that in the next release, I will add a property to specify the Dll's folder.

Hope this helps,

Morgan

Related 'Python for Delphi' Links on this site:

bullet tutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bullet code - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullet tutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bullet discussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bullet animated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bullet example and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

posted @ 2011-03-25 23:06  babykick  阅读(3503)  评论(0编辑  收藏  举报