Python for Delphi |
|
![]() |
The Name property of the PythonDelphiVar component is, of course, how Delphi refers to the PythonDelphiVar component. |
![]() |
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.
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:
procedure TForm2.Button2Click(Sender: TObject); begin PyExeFile('unit2.py', PE); end;
The Python side
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?
procedure TForm2.Button3Click(Sender: TObject); begin showmessage( HEADERVAR.ValueAsString +#13+ RESULTVAR.ValueAsString ); end;
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;
yields 13. Success!
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?
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:
implementation uses AndyDelphiPy, ComCtrls, pythonAtom; var aCustomer : OleVariant;
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.
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:
Success!
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.
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).
plus
Advanced topic: Here is a discussion and tips on python for delphi deployment issues.
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.
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 dont 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/