Python4Delphi

Python for Delphi (P4D) is a set of free components that wrap up the Python DLL into Delphi and Lazarus (FPC). 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 like scripting. P4D provides different levels of functionality:
- Low-level access to the python API
- High-level bi-directional interaction with Python
- Access to Python objects using Delphi custom variants (VarPyth.pas)
- Wrapping of Delphi objects for use in python scripts using RTTI (WrapDelphi.pas)
- Creating python extension modules with Delphi classes and functions
- Generate Scripts in maXbox from Python Installation
P4D makes it very easy to use python as a scripting language for Delphi applications. It also comes with an extensive range of demos and useful (video) tutorials.
For more insights I did an Overview of the Project in a 7 pages handout.
Overview of P4D: http://www.softwareschule.ch/download/maxbox_starter86.pdf
How to use Python4Delphi
So a very first simple approach is to call the Python dll without a wrapper or mapper e.g. call the copyright function:
//if fileExistst(PYDLLPATH+ ‘python37.dll’;
function getCopyRight: PChar;
external ‘Py_GetCopyright@C:\maXbox\EKON25\python37.dll stdcall’;
Then we call the function with a pre-test:
function IsDLLOnSystem(DLLName:string): Boolean;
var ret: integer;
good: boolean;
begin
ret:= LoadLibrary(pchar(DLLNAME));
Good:= ret>0;
if good then FreeLibrary(ret);
result:= Good;
end;if isDLLOnSystem(PYDLLPATH+PYDLLNAME) then begin
showmessage(‘py dll available’);
writeln(getCopyRight)
end;
Copyright © 2001–2019 Python Software Foundation.
All Rights Reserved.
Copyright © 2000 BeOpen.com.
All Rights Reserved.
Copyright © 1995–2001 Corporation for National Research Initiatives.
All Rights Reserved.
Copyright © 1991–1995 Stichting Mathematisch Centrum, Amsterdam.
All Rights Reserved.
We also use to invoke a Python script as an embedding const and use the dll functionality of Import(‘PyRun_SimpleString’);
procedure InitSysPath;
var _path: PPyObject;const Script =
‘import sys’ + sLineBreak+
‘sys.executable = r”%s”’ + sLineBreak+
‘path = sys.path’ + sLineBreak+
‘for i in range(len(path)-1, -1, -1):’ + sLineBreak+
‘ if path[i].find(“site-packages”) > 0:’ + sLineBreak+
‘ path.pop(i)’ + sLineBreak+
‘import site’ + sLineBreak+
‘site.main()’ + sLineBreak+
‘del sys, path, i, site’;begin
if VenvPythonExe <> ‘’ then
ExecString(AnsiString(Format(Script, [VenvPythonExe])));
_path := PySys_GetObject(‘path’);
if Assigned(FOnSysPathInit) then
FOnSysPathInit(Self, _path);
end;
The best way to learn about how to use Python for Delphi is to try the extensive range of demos available. Also studying the unit tests for VarPyth and WrapDelphi can help understand what is possible with these two units.
Lets start with the VarPyth.pas unit.
This allows you to use Python objects like COM automation objects, inside your Delphi source code. This is a replacement, bugfixed of the former PythonAtom.pas that uses the new custom variant types introduced since Delphi6.
You may use these Python Variants in expressions just as you would any other Variants or automation e.g.:
var a: Integer; v: Variant;
begin
v:= VarPythonEval(‘2 ** 3’);
a:= v;
The functions provided by this unit VarPyth.pas are largely self-explanatory.
What about the WrapDelphi.pas unit?
You can use P4D to create Python extension modules too that expose your own classes and functions to the Python interpreter. You may package your extension with setup-tools and distribute it through PyPi. So if you have an existing object or unit in Delphi that you’d like to use in Python but you don’t want to modify that object to make it Python-aware, then you can wrap that object just for the purposes of supplying it to Python with a package.
Using TPyDelphiObject you can wrap any Delphi object exposing published properties and methods. Note that the conditional defines TYPEINFO and METHODINFO need to be on.
As an example, if you have an existing Delphi class simply called TRGBColor:
TRGBColor = class
private
fRed, fGreen, fBlue: Integer;
public
property Red: read fRed write fRed;
property Green: read fGreen write fGreen;
property Blue: read fBlue write fBlue;
end;
You want to use Color within some Python code but you don’t want to change anything about the class. So you make a wrapper inherited from TPyObject that provides some very basic services, such as getting and setting attributes of a Color, and getting a string representation:
TPyColor = class(TPyObject)
private
fColor: TRGBColor;
public
constructor Create( APythonType: TPythonType ); override;
// Py Basic services
function GetAttr(key: PChar): PPyObject; override;
function SetAttr(key: PChar; value:PPyObject): Integer; override;
function Repr: PPyObject; override;
end;
The project in the sub-directory Delphi generates a Python extension module (a DLL with extension “pyd” in Windows) that allows you to create user interfaces using Delphi from within Python. The whole VCL (almost and maybe) is wrapped with a few lines of code! The small demo TestApp.py gives you a flavor of what is possible. The machinery by which this is achieved is the WrapDelphi unit.
The sub-directory DemoModule demonstrates how to create Python extension modules using Delphi, that allow you to use in Python, functions defined in Delphi. Compile the project and run test.py from the command prompt (e.g. py test.py). The generated pyd file should be in the same directory as the Python file. This project should be easily adapted to use with Lazarus and FPC.
The subdirectory RttiModule contains a project that does the same as the DemoModule, but using extended RTTI to export Delphi functions. This currently is not supported by FPC.
The unit PythonEngine.pas is the main core-unit of the framework. You are responsible for creating one and only one TPythonEngine. Usually you just drop it on your main form. Most of the Python/C API is presented as member functions of the engine.
type
TPythonVersionProp = record
DllName : string;
RegVersion : string;
APIVersion : Integer;
end;
…
Py_BuildValue := Import(‘Py_BuildValue’);
Py_Initialize := Import(‘Py_Initialize’);
PyModule_GetDict := Import(‘PyModule_GetDict’);
PyObject_Str := Import(‘PyObject_Str’);
PyRun_String := Import(‘PyRun_String’);
PyRun_SimpleString := Import(‘PyRun_SimpleString’);
PyDict_GetItemString := Import(‘PyDict_GetItemString’);
PySys_SetArgv := Import(‘PySys_SetArgv’);
Py_Exit := Import(‘Py_Exit’);
…
You will need to adjust the demos accordingly, to successfully load the Python distribution that you have installed on your computer.
Let’s take a last look at the functionality of PyRun_SimpleString mentioned first within the const script.
http://www.softwareschule.ch/examples/1016_newsfeed_sentiment_integrate2.txt
PyRun_SimpleString: function(str: PAnsiChar): Integer; cdecl;
We see that we have to pass a PAnsiChar in cdecl convention and map to ExecString (PyRun_String:):
procedure TPythonEngine.ExecString(const command : AnsiString);
begin
Py_XDecRef( Run_CommandAsObject( command, file_input ) );
end;

Conclusion
The P4D library provides a bidirectional bridge between Delphi and Python. It allows Delphi applications to access Python modules and run Python scripts. On the other side it makes Delphi/Lazarus objects, records, interfaces, and classes accessible to Python, giving Python the ability to use this methods.
Before you try the demos please see the Wiki topic “How Python for Delphi finds your Python distribution” at
https://github.com/pyscripter/python4delphi/wiki/FindingPython
You will need to adjust the demos accordingly, to successfully load the Python distribution that you have installed in your PC, e.g. C:\Users\max\AppData\Local\Programs\Python\Python36\.
PythonEngine: The core of Python for Delphi. Provides the Python
API with dll mapper and runtime configuration.VarPyth: VarPyth wraps Python types as Delphi custom variants.
WrapDelphi: Uses RTTI (in a DLL) so you can use Delphi objects
from Python without writing individual wrapper
classes or methods.TPythonGUIInputOutput: Provides a Python console you can drop on a form and execute a Python script from a memo.
The TPythonEngine class is the single global engine block shared by all Python code. VarPyth wraps Python types as Delphi custom variants. VarPyth requires at least Delphi v6.
This Tutorials folder contains text and video, webinar tutorials accompanied with slides and demo source code. Next time I’ll show a few bidirectional demos with implementation details.
Wiki P4D topics
- Installation
- Supported Platforms
- How Python for Delphi finds your Python distribution (read before trying the demos)
Learn about Python for Delphi
Dealing with internals of Python means also you get the original stack-trace errors back like (TPythonEngine.RaiseError;):
File “C:\Users\max\AppData\Local\Programs\Python\Python36\lib\site-packages\pandas\core\indexing.py”, line 1304, in _validate_read_indexer
raise KeyError(f”{not_found} not in index”)
KeyError: “[‘id’, ‘links’, ‘published_parsed’, ‘published’, ‘title_detail’, ‘guidislink’, ‘link’, ‘summary_detail’] not in index”
The script can be found:
http://www.softwareschule.ch/examples/1016_newsfeed_sentiment_integrate2.txt
Fixed and Forked github above. Next I want to show a simple integration from Python in a Delphi Scripter with maXbox4. In a memo form like textblock, we include python-code to execute it later with the help of a command line interface (CLI+argument vectors) and route the result back to the maXbox console.
We use a sentiment analysis to raise the question: should I read the newspaper today?
{# ImageAI is a Python library built to empower Computer Vision from imageai.Detection import ObjectDetection #Using TensorFlow backend. ##!/usr/bin/env python import wget and integrate in maXbox control } //The question is: should I read the newspaper today? program Unified_NewsfeedSentiment_Summary2; const C=CRLF; const SCRIPTNAMEP= '1016_newsfeed_sentiment.py'; const DETECTOUTFILE= 'newsfeed_titlesout22.txt'; Const PYSCRIPT6 = 'import nltk '+C+ 'from nltk.sentiment.vader import SentimentIntensityAnalyzer '+C+ 'import wget '+C+ 'import sys, datetime as dt '+C+ '#nltk.download("vader_lexicon") '+C+ 'import feedparser '+C+ 'import pandas as pd '+C+ 'pd.set_option("max_colwidth", 400) '+C+ 'import numpy as np '+C+ 'print("this first line after imports") '+C+ ' '+C+ ' '+C+ 'def GraphViz(node): '+C+ ' d = Graph(node) '+C+ ' '+C+ ' from graphviz import Digraph '+C+ ' dot = Digraph("Graph", strict=False) '+C+ ' dot.format = "png" '+C+ ' '+C+ ' def rec(nodes, parent): '+C+ ' for d in nodes: '+C+ ' if not isinstance(d, dict): '+C+ ' dot.node(d, shape=d._graphvizshape) '+C+ ' dot.edge(d, parent) '+C+ ' else: '+C+ ' for k in d: '+C+ ' dot.node(k._name, shape=k._graphvizshape) '+C+ ' rec(d[k], k) '+C+ ' dot.edge(k._name, parent._name) '+C+ ' for k in d: '+C+ ' dot.node(k._name, shape=k._graphvizshape) '+C+ ' rec(d[k], k) '+C+ ' return dot '+C+ ' '+C+ ' '+C+ 'news_feed=feedparser.parse("http://feeds.bbci.co.uk/news/world/rss.xml")'+C+ 'sid = SentimentIntensityAnalyzer() '+C+ 'descriptions=["very negative","negative","slightly negative", \' +C+ ' "neutral","slightly positive", "positive","very positive"] '+C+ 'num_descriptions = len(descriptions) '+C+ 'frontpage = pd.DataFrame() '+C+ 'output_path = sys.argv[1] '+C+ ' '+C+ 'def compound_polarity_descript(compound): '+C+ ' return descriptions[int(((1 + compound) / 2) * num_descriptions)] '+C+ ' '+C+ '#wget.download(url, out=destination) #, useragent= "maXbox4") '+C+ 'for entry in news_feed.entries: '+C+ ' ss = sid.polarity_scores(entry.title + "\n" + entry.summary) '+C+ ' series = pd.Series( '+C+ ' [ '+C+ ' ss["neg"], '+C+ ' ss["neu"], '+C+ ' ss["pos"], '+C+ ' ss["compound"], '+C+ ' compound_polarity_descript(ss["compound"]), '+C+ ' entry.title, '+C+ ' entry.summary '+C+ ' ], '+C+ ' index=[ '+C+ ' "neg","neu","pos", '+C+ ' "compound", '+C+ ' "human", '+C+ ' "title", '+C+ ' #print(eachItem["name"],": ",eachItem["percentage_probability"]) '+C+ ' "summary" '+C+ ' ]) '+C+ ' frontpage=pd.concat([frontpage,series.to_frame().T],ignore_index=True)'+C+ ' '+C+ 'alist=[] '+C+ 'for count,entry in enumerate(news_feed.entries): '+C+ ' alist.append(str(count)+": "+entry.title+": "+ '+C+ ' str(sid.polarity_scores(entry.title+entry.summary)["compound"])) '+C+ ' '+C+ 'finallist = "\n".join(alist) '+C+ 'with open(output_path, "w") as file: '+C+ ' file.write("BBC-News Sentiment of "+str(dt.datetime.now())+ '+C+ ' "\n"+str(finallist)+ '+C+ ' "\n"+str(dt.datetime.utcnow())) '+C+ ' '+C+ 'compound_frontpage = frontpage["compound"].mean(skipna = True) '+C+ 'print("\n") '+C+ 'print(frontpage) '+C+ 'print("News today: "+compound_polarity_descript(compound_frontpage)) '+C+ 'print("integrate newsfeed sentiment detector compute ends...") '; //*) const ACTIVESCRIPT = PYSCRIPT6; var RUNSCRIPT, outputPath: string; startTime64, endTime64, freq64: Int64; begin //@main //-init env maxform1.console1click(self); memo2.height:= 205; QueryPerformanceFrequency(freq64); //-config saveString(exepath+SCRIPTNAMEP, ACTIVESCRIPT); sleep(600) //outputPath:= '.\crypt\output\'+DETECTOUTFILE; outputPath:= 'C:\maXbox\EKON24\crypt\output\'+DETECTOUTFILE; if fileExists(exepath+SCRIPTNAMEP) then begin RUNSCRIPT:= exepath+SCRIPTNAMEP; QueryPerformanceCounter(startTime64); //writeln(getDosOutput('python '+RUNSCRIPT+' '+outputpath, exePath)); writeln(getDosOutput('py '+RUNSCRIPT+' '+outputpath, exePath)); QueryPerformanceCounter(endTime64); println('elapsedSeconds:= '+floattostr((endTime64-startTime64)/freq64)); openFile(outputPath) //} end; end.
BBC-News Sentiment of 2021–06–07 17:06:39.438889
0: Pakistan train accident: Dozens killed in Sindh collision: -0.9001
1: Unlocking: India states start reopening amid dip in Covid cases: 0.25
2: Norway police say body on shore is Kurdish-Iranian boy who died in Channel: -0.7003
3: Chinese birth-control policy could cut millions of Uyghur births, report finds: -0.4939
4: Jeff Bezos and brother to fly to space in Blue Origin flight: 0.296
5: Apple employees rally against office working plan: 0.0
6: Greek islands aim to go ‘Covid-free’ to welcome back tourists: 0.4588
7: Afghan policewomen abuse: US and EU urge inquiry: -0.9493
8: Egerton Ryerson statue toppled at Canada indigenous school protest: -0.25
9: Peru election: Fujimori’s lead narrows as rural votes come in: 0.5719
10: The Rolling Stones and Tom Jones call for streaming reforms: 0.0
11: Ukraine’s Euro 2020 football kit provokes outrage in Russia: -0.6808
12: Why Kim Jong-un is waging war on slang, jeans and foreign films: -0.93
13: Kenya’s Thandiwe Muriu: Standing out in camouflage: 0.7269
14: Daniel Ellsberg: The 90-year-old whistleblower tempting prosecution: -0.3182
15: ‘My foggy glasses solution helped me through Covid’: 0.7579
16: ‘Clothes are torn, worn out — I can’t find work gloves’: -0.25
17: The couple rescuing the house they bought by accident: -0.4767
18: Letter from Africa: How Zimbabwe is still haunted by Robert Mugabe: -0.9001
19: Egerton Ryerson: Statue toppled of architect of ‘shameful’ school system: 0.2732
20: Prince Harry and Meghan: California residents react to birth of royal baby girl: 0.0
21: Pakistan train cash: Footage shows collision aftermath: -0.7845
22: Ukraine conflict: The couple that’s survived seven years of war: 0.5267
23: British Normandy Memorial unveiled on 77th D-Day anniversary: -0.5574
24: Venice residents in environmental protest against first post-Covid cruise ship: -0.765
2021–06–07 15:06:39.438889
Create Python extension modules using P4D
You can use P4D to create Python extension modules that expose your own classes and functions to the Python interpreter. You can package your extension with setuptools and distribute it through PyPi.
- The project in the subdirectory Delphi generates a Python extension module (a dynamic link library with extension “pyd” in Windows) that allows you to create user interfaces using delphi from within python. The whole VCL (almost) is wrapped with a few lines of code! The small demo TestApp.py gives you a flavour of what is possible. The machinery by which this is achieved is the WrapDelphi unit.
- The subdirectory DemoModule demonstrates how to create Python extension modules using Delphi, that allow you to use in Python, functions defined in Delphi. Compile the project and run test.py from the command prompt (e.g. py test.py). The generated pyd file should be in the same directory as the python file. This project should be easily adapted to use with Lazarus and FPC.
- The subdirectory RttiModule contains a project that does the same as the DemoModule, but using extended RTTI to export Delphi functions. This currently is not supported by FPC.
The Unit ‘VarPyth’
VarPyth wraps Python types as Delphi custom variants. VarPyth requires at least Delphi v6.
You can use these Python Variants in expressions just as you would any other Variant. e.g.
var a: Integer; v: Variant;
begin
v :=VarPythonEval('2 ** 3');
a := v;
The functions provided by this unit are largely self-explanatory. See the interface section of VarPyth.pas.
http://py4d.pbworks.com/w/page/9174534/VarPyth http://py4d.pbworks.com/w/page/9174535/Wrapping%20Delphi%20Objects
Appendix: Catalog of the Demos:
{*----------------------------------------------------------------------------*)
Demo01 A simple Python evaluator
Demo02 Evaluate a Python expression
Demo03 Defining Python/Delphi vars
Demo04 Defining Python/Delphi vars (advanced case)
Demo05 Defining a new Module
Demo06 Defining a new Type
Demo07 Using Delphi methods as Python functions
Demo08 Using Delphi classes for new Python types
Demo09 Making a Python module as a Dll
Demo10_FireDAC Database demo using FireDAC
Demo11 Using Threads inside Python
Demo16 Using a TDelphiVar or Module methods
Demo17 Using variant arrays of 2 dimensions
Demo19 C++ Builder: this is a replicate of the Delphi Demo05
Demo20 C++ Builder: this is a replicate of the Delphi Demo08
Demo21 Using Events in TPythonModule or TPythonType
Demo22 Using Threading, Windows Console and Command line arguments
Demo23 Using Threading and Delphi log window
Demo25 Using VarPyth.pas
Demo26 Demo8 revisited to allow the new Python type to be subclassed
Demo27 Container indexing
Demo28 Iterator
Demo29 Using Python Imaging Library (PIL)
Demo30 Using Named Parameters
Demo31 Using WrapDelphi to access Delphi Form attributes
Demo32 Demo08 revisited using WrapDelphi
Demo33 Using Threads inside Python
Demo34 Dynamically creating, destroying and recreating PythonEngine.
Originally published at http://maxbox4.wordpress.com on June 7, 2021.
Install a 32 bit package module in a 64 bit environment:
1. Change to your 32 bit path with cd:
cd C:\Users\Max\AppData\Local\Programs\Python\Python36–32>
2. Call the Pip (e.g. faker module) explicitly with python.exe:
python -m pip install fakerAnd it runs:
Downloading https://files.pythonhosted.org/packages/27/ab/0371598513e8179d9053
911e814c4de4ec2d0dd47e725dca40aa664f994c/Faker-9.9.0-py3-none-any.whl (1.2MB)…..
The other way round is VCL4Python. Delphi’s VCL library as a Python module for building Windows GUI:
