|
+Using Delphi and Python togetherTalk for the Australian Delphi User Group (ADUG), July and
August 2001 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:
return to main Andy Patterns home page Introduction and TutorialIntroductionCreate 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. InstallationInstall Python for win32 Official distribution installer Then install the Python for Delphi components from
so you finish up with the following palette: 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 testA simple Python evaluator:
PythonEngine1.ExecStrings( Memo1.Lines );
click the button and 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 goesEvery 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.
PythonDelphiVarsTo 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: The most important PythonDelphiVar component property is VarName property which is the name by which python 'sees' this 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.
procedure TForm2.Button2Click(Sender: TObject); begin PyExeFile('unit2.py', PE); end;
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; 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)
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;
Direct access to Python ClassesFirst 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 TPythonAtomBasically 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.
implementation uses AndyDelphiPy, ComCtrls, pythonAtom; var aCustomer : OleVariant;
procedure TfrmMain.FormShow(Sender: TObject); begin PyExeFile('YourPythonApp.py', PE);
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! Passing python objects as parametersYour 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. DeploymentEasy - 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).
Advanced topic: Here is a discussion and tips on python for delphi deployment issues. Andy's helpful minor extensions to Python-for-DelphiThese 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 pythonIf 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 There are no Python understands named parameters e.g. In a method call, 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 The statement You can return multiple items at once e.g. Other class files/modules are imported using Both Python and JPython (Java Python, now called Jython) are open source, free and available from www.python.org :-) Related 'Python for Delphi' Links on this site:
return to main Andy Patterns home page Ideas from you!Please add your ideas on this article and Python for Delphi here in this public guest book and share your thoughts with other visitors.
CommentsThis article doesn't cover the most recent enhancements to python for delphi - I've yet to set aside time to investigate and understand them - if someone could write a few paragraphs on how I could enhance what I have already written with the more modern stuff - that would be greatly appreciated. Though I have set up a 'latest techniques' link on this page and entered in some initial thoughts... Andy Bulka http://www.atug.com/andypatterns
CommentsIs there a role for using Delphi as a GUI to assist in writing python scripts? Take, for example, the power, flexibility and simplicity of python lists, dictionaries and tuples: a delphi list box or listview could receive input from the Py trainee and insert same into script templates in the appropriate way depending on purpose of list, ensuring correct syntax. As Py trainees see how Delphi talks to Python, they can be encouraged to work it the other way round and seek out developers who know Delphi for the GUI end of their own scripts or apps. I am developing a framework for modelling common law worlds, or spheres of activity, as some of our Canadian judges like to call each "area of law". Obviously, a lifetime would be insufficient for me to make a dent in the details of practice procedures (tasks, steps, activities, associated prescribed forms etc) for each sphere. So it would be quite useful for me to have the domaine experts, in their respective areas of practice, have a way to create scripts for embedding in my framework, by country, state/province, area of law, procedure type and procedure ID. Those scripts could be taken back into Delphi for storage in datasets and off we go with a databank of 'how to's, potentially for the entire common law world. Until I picked up Learning Python a few weeks ago (for a buck in a book bin) there was, to say the least, a huge gap in my business model. Now, I just have to induce a few thousand lawyers, judges, law students and para legals that Python is so intuitive they can be part of what I might call the Great Unified Legal Procedures Model (GULP'M!). I've been through the 24 demos in Python4Delphi and see tremendous potential, but know I am missing a lot. Your talk to the ADUG helps, especially with respect to the PythonDelphiVar comp. But I can use a lot more. Thanks for all the work you've done on this, and for passing along the AndyDelphiPy.pas file. cheers, mate! Alf Rushworth LeXX:Talk Laware (my tryout name this week) Smart.LX@shaw.ca OR alf.rushworth@shaw.ca
CommentsRockin! I´m so happy that I discovered yur site by accident, now I can work further on my DELPHI/Python application due to your mini tutorials Cheers Nhytro |