![]() |
AW: Wie Embedded-Python und PIP?
Hallo himitsu,
ich bin durch einen Verweis - bzw. Antwort - von dir auf dieses Thema aufmerksam geworden. ![]() Ich kenne bisher nur venv. Nun möchte ich die Vor- und Nachteile gegenüber "Embedded" untersuchen. Das Ziel ist klar: Man will ohne viel Aufwand und Stolperfallen eine Installation auf den Benutzer-PC hinbekommen. Bei beiden Varianten hat man ein Verzeichnis, wo alles - was für Python benötigt wird - enthalten ist. Wenn ich es richtig verstehe, dann braucht man bei Embedded kein Skript zum Starten, wie es bei venv notwendig ist. Dafür braucht man sich bei venv nicht um die Installation von pip kümmern. Was aber auch nur einmal erfolgen muss. Danach kann pip sich ja selbst updaten. Bei Embedded scheint es zu genügen, dass man den Path zur gewünschten python.exe in P4D über ein relative Path-Angabe setzt. Mich verunsichert aber, dass du schriebst: Zitat:
|
AW: Wie Embedded-Python und PIP?
Nja, erstmal haben wir ein paar Bugfixe im python4delphi. (könnte bei Gelegenheit mal nachsehn, wie ich unseren firmeninternen Github-Fork in einen Öffentlichen umkopiere)
Dann ist das Ganze noch in eine Python-Klasse eingebettet, welche das Suchen/Laden der DLL übernimmt, die eigentliche Python-Klasse und ein paar Module/Funktionen intern erstellt/verwaltet. Im Endeffekt kommt bei der Suche sowas raus, was man auch manuell zuweisen könnte. (aus einer der DFM der Demoanwendungen kopiert)
Code:
Für das PIP im Embedded hatte ich zwei Lösungen gefunden
object PythonEngine1: TPythonEngine
DllName = 'python39.dll' DllPath = 'E:\EXE\Python' APIVersion = 1013 RegVersion = '3.9' UseLastKnownVersion = False ... * PIP als Modul im PIP selbst installiert (wird über ein initialies Python-Script "teilweise" eingerichtet) * oder PIP als Python-Application (PYZ) * Letzeres haben wir bei uns im Einsatz [das Python] Infos: ![]() Quelle: ![]() Download: ![]() [als App] Quelle: ![]() Download: ![]() Nutzung:
Delphi-Quellcode:
python.exe pip.pyz <command> <params>
[als Modul] Installaionsscript: ![]()
Delphi-Quellcode:
python.exe get-pip.py
Nutzung:
Delphi-Quellcode:
python.exe -m pip <command> <params>
z.B.
Delphi-Quellcode:
python pip.pyz install <modulname>
In der python38._pth die Suchpfade für's PIP hinzufügen (für Suche der Python-Scripte&Module)
Code:
und dann auch nochmal zur Laufzeit der Komponente/DLL (für Helper-EXEn welche die Module mitbringen)
python38.zip
. Addons Lib\site-packages
Delphi-Quellcode:
// PIP-ScriptHelper in Suchpfad
P := EnviromentVar('PATH', False); S := ExtractFilePath(FPythonDLLFilename) + 'Scripts'; if DirectoryExists(S) and not ContainsText(P, S) then SetEnvironmentVariable('PATH', PChar(S + ';' + P)); Probleme haben wir beim Embedded aber mit Pyhon-Komkomenten aus'm PIP, welche wiederum selbst DLLs laden, da diese DLLs, nach Ausführung des Scripts, "meistens" garnicht oder nicht richtig wieder entladen werden. Und beim nächsten Ausführen eines anderen Scriptes es dann blöde Fehler gibt, auch bis dahin, dass garnichts mehr geht. Oder es mindestens Speicherlecks gibt, welche sich bis zum Kollaps aufsummieren. Hier ein Auszug der Info-Datei, welche ich mal bei uns im Projekt hinterlegt hab (bissl Firmeninternes entfernt/anonymisiert)
Code:
Download : https://www.python.org/downloads/windows/
-> "Windows embeddable package (32-bit)", da Delphi&UnserProgramm als Win32 https://www.python.org/ftp/python/3.8.10/python-3.8.10-embed-win32.zip aktuelle Version : Python 3.8.10 Embedded 32-bit Python-UPGRADE : aktuelles siehe "Download" ins DIESES Verzeichnis "Python3" entpacken Kann alles gelöscht/überschrieben werden, siehe "restliche *.pyd" ABER in der python**._pth die Suchpfade beachten, siehe "python38._pth" ... und zuletzt siehe "python pip.pyz" LÖSCHEN: charset_normalizer\md.cp38-win32.pyd und charset_normalizer\ md__mypyc.cp38-win32.pyd werden durch PIP in den Download eingefügt und führen zu Problemen. https://www.delphipraxis.net/1524779-post6.html SourceCode der Python**.dll -> https://github.com/python/cpython -> \Include\pylifecycle.h [[ Anleitung PIP : Package Installer for Python ]] Installation siehe "pip.pyz" Explorer > ProgrammVerzeichnis\Python3 in Adressleiste : CMD [Enter] python pip.pyz help Hilfe python pip.pyz list Liste des Installierten python pip.pyz search Suchen : geht nicht mehr, aber siehe https://pypi.org/search bzw. https://pypi.org/classifiers/ python pip.pyz install <modulname> Installieren python pip.pyz uninstall <modulname> Deinstallieren python pip.pyz show <modulname> Infos: Abhängigkeiten, Version, Beschreibung, Quelle usw. python pip.pyz check Abhängigkeiten prüfen [[ Einiges vom Verzeichnisinhalt des "Python3" ]] python.exe = nutzen wir im Programm nicht (aber für PIP oder z.B. manuelles Ausführen der DebugBackup-Datei *.py aus Programm) python3.dll = Forward-DLL mit Weiterleitung zur jeweiligen python3*.dll python38._pth = Python-Config : Suchpfade und optional ein InitialScript "python38.zip" , "." und unser Zusatz für's PIP "Lib\site-packages" , sowie ein "Addons" python38.dll = [Version 3.8.10] das Python python38.zip = [Version 3.8.10] viele StandardModule (os, io, datetime, zip, base64, xml, json, csv, email, ...) = ist ein site-package (wird über python39._pth geladen) und enthält viele *.pyc (compiled bytecode) diese können ebenfalls in den eigenen Scripten genutzt werden *.pyd = vorkompilierte Python-Module (DLLs), zur Nutzung innerhalb der Scripte *.pyc = kompiliertes Python-Script *.py = Python-Script *.pyz = Python Zipped Executable ist wohl sowas Ähnliches wie *.pyd restliche *.pyd = stammen aus der python-3.8.10-embed-win32.zip, also ebenfalls Standard-Zeugs, wie die python38.zip -> bis auf DIESE Info-Datei, bzw. die Versions-Info, die "pip.pyz" und unsere angepasste "python38._pth" __pycache__\ = [gitignore] Cache der Python-EXE/DLL (kompilierte *.pyc der *.py) Addons\ = Zusätzliche Scripte von uns (siehe Nachfolgend "Addons") Lib\site-packages\ = Python-Module via PIP installiert Scripts\ = Tools vom PIP pip.pyz = [Version 3.6] Package Installer for Python - der Paketmanager (siehe "Anleitung PIP") Quelle: https://pip.pypa.io/en/stable/installation/#get-pip-py [als Python-Anwendung] Download: https://bootstrap.pypa.io/pip/pip.pyz muß ins Hauptverzeichnis, geht nicht aus .\Addons\ "CMD: python.exe pip.pyz <command> <params>" [als Python-Modul] Alternativ: https://bootstrap.pypa.io/get-pip.py dieses runterladen und installieren "python.exe get-pip.py" landet im .\Lib\site-packages\ "CMD: python.exe -m pip <command> <params>" |
AW: Wie Embedded-Python und PIP?
Beim Python4Delphi ist eigentlich eine VCL-Erweiterung dabei, aber wir hatten noch nicht rausbekommen, wie man die benutzt.
Klingt ja so, als wenn sie die VCL des eigenen Programms nutzt, während das Python-Script in der Python-DLL läuft. Es gibt auch noch ein VCL4Python und FMX4Python, welche die VCL/FMX selbst als DLL mitbringen und was man auch in der Python-EXE nutzen kann, nicht nur Embedded. |
AW: Wie Embedded-Python und PIP?
Danke himitsu!
Da lieferst du mir eine Menge Input :thumb:. Den muss ich erstmal verarbeiten, bevor ich zu chaotisch nachfrage. Bisher habe ich Python nur für mich benutzt und das auch nur getrennt von Delphi-Programmen. Was du schreibst klingen sehr danach, dass ihr leitvolle Erfahrung sammeln musstet. Das macht mich schon Mal vorsichtiger. Zwischenzeitlich habe ich gelernt, dass venv nicht unabhängig vom global installierten Python ist (ich war so blauäugige das zu glauben :oops:). Nun habe ich gesehen, dass P4D nach einer pyvenv.cfg sucht und darin steht ein absoluter Verweis zum globalen Python. Bei mein heutigen Versuchen musste ich feststellen, dass wenn ein Skript unter venv in ein Kommandozeilenfenster läuft, es nicht bedeutet, dass es auch von P4D-Demo1 Skript-Memo aus läuft. Bis zum produktiven Einsatz scheint es noch ein weiter Weg zu sein. |
AW: Wie Embedded-Python und PIP?
Nja, wir hatten ursprünglich Python als "Embedded" mitgeliefert (nur nannte es damals noch niemand so).
Also als ZIP runtergeladen und die EXE (inkl. DLL und Co.) als Unterverzeichnis beim Programm. Das aktuelle Script wurde als .py-Datei auf die Festplatte geschieben, dann die Python.exe gestartet, die Datei als Parameter übergeben und dann via ShellExecute/CreateProzess die Ausgabe in eine Datei umgeleitet. Später der Ausgabestream direkt im CreateProzess übergeben und die Ausgabe in ein unsichtbares TMemo, was dann weiterverwendet wurde und sich beim Dedbuggen anzeigen ließ. Zuletzt dann Python4Delphi eingeführt, wo das Script als Stream/String im Programm bleibt und auch die Ausgabe in einem Stream/String landet. Zusätzlich noch ein paar Variablen und Delphi-Funktionen reingegeben, welche sich im Python nutzen lassen. Auch lässt sich hier intern schöner die Fehlerbehandlung machen. Also vorher ein SyntaxCheck und bei einem Fehler erweiterte Fehlerinfos von der DLL abrufen. Dann ging es darum auch Fremdcode zu nutzen (bisher vorwiegend nur selbsterstellte PythonScripte) und da sind wir dann zu PIP übergegangen, da es manuell schnell umständlich wird, mit all den Abhängigkeiten. Als allerletztes dann noch die Ausgabe im Python auf Unicode umgestellt, denn rein ging es schon als Unicode, aber die Ausgabe sollte eigentlich UTF-8 sein (war aber ANSI). Bei der EXE war es so, dass die Python-Instanz immer neu ist und ein Script niemals Auswirkungen auf nachfolgende Scripte hat. -> Wurde z.B. in einem Script vergessen eine Variable zu initialisieren, dann was diese Variable in der EXE immer leer, aber nun in der DLL hatte sie den Wert einer vorhergehenden Ausführung, was das Verhalten manchmal plötzlich veränderte. Da wir damals es nicht geschafft hatten, die Instanz (Variablen und geladene Module) zurückzusetzen, wird aktuell nach jeder Ausführung eines Scriptes die Instanz freigegeben und die Python-DLL entladen. Beim nächsten Laden war somit alles wieder neu/leer. Das lief bisher gut und auch flott. Dann versuchte jemand PySide2 und shiboken2 nutzen zu wollen. Ersteres lädt selbst DLLs und entlädt sich nicht mehr. (Entwickler für Scriptsprachen sind einfach nur faule Schlampen ... siehe meine Signaur) Diese DLLs haben auch noch eine Referenz auf die python3.dll, welche wiederrum die python38.dll (welche wir geladen haben und nutzen) referenziert, womit sich alles gegenseitig im RAM festkrallt, nachdem wir unsere Referenz auf die python38.dll freigaben und dachten es wäre nun entladen. Erstmal hatte hier Python4Delphi einen Bug, so dass es in der Finalisazion beim Entladen der einen DLL einen Exception gibt (das ist OK), aber nachfolgend anderes vergessen wird zu finalisieren und freizugeben. (try-finally vergessen) Dennoch bleibt irgendwas im RAM (auch wenn manuell alle DLLs entladen werden), was dann beim nächsten Laden und der Initialisation zu einer Exception führt und es nicht mehr erlaubt weitere Scripte auszuführen (python4delphi bricht beim Laden und starten des Scripts ab). Ja, ich kann manuell diese DLLs in der richigen Reihenfolge entladen (testweise für PySide2 gemacht), aber da der Code universell sein muß, weil ich garnicht weiß, welche Komponenten/PythonModule zukünftig genuzt werden. Hier hab ich mich nun mühevoll in Windows "gehackt", um an die LadeListen der DLLs zu kommen (inkl. Rehenfolge/Uhrzeit und dem Grund des Ladens, also manuell oder durch automatische Abhängigkeit) Das wird nun genutzt die FremdDLLs zu finden und zu entladen. Aber wie gesagt, irgendwo bleibt noch was zurück und knallt nachfolgend. Das hatte ich noch nicht gefunden, drum hängt das Projekt hier erstmal, da Anderes wichtiger. |
AW: Wie Embedded-Python und PIP?
Zitat:
Wir haben ein Kunden, der gerne mein Delphi-Programm durch Python-Skripts an seine Bedürfnisse anpassen möchte. Durch deinen Erfahrungsbericht ist mir bewusst geworden, wie wichtig das "Abstand halten" ist, damit man sich nicht Probleme auf den Tisch zieht, die andere verursachen. Stelle mir nun eine TCP-Client Anbindung an meinen schon vorhandenen TCP-Server vor. Da kann der Kunde draußen spielen bis der Arzt kommt, ohne mein Programm mit in den Abgrund reißen zu können. :-) |
AW: Wie Embedded-Python und PIP?
Es gibt/gab auch noch die Windows Scripting API.
Das ist eine API, welche man in seinem Programm nutzt, wo sich dann auch andere Sprachen registrieren und somit genutzt werden können. Diese zu implementieren war nicht das Problem, womit man erstmal JavaScript und VBScript zur Verfügung hat. Über C# gibt es noch irgendwie eine Implementation für PowerShell und Python (wobei FinalBuilder eher vom Python abrät, laut dem Kommentar in deren ScriptingFenstern) Die mir bekannte Implementation vom ActiveScripting hatte ich nicht zum Laufen bekommen ... k.A. ob es da noch drin vorhanden ist, seit es die kostenlose Version nicht mehr gibt und in der aktuellen Demo ist es auch nicht drin. (nach der Installation ist keine Registrierung zu finden) Auch es gibt noch dutzende andere ScriptSprachen, welche dort registriert werden können ... angeblich auch irgendein PascalScript (diesbezüglich hatte ich noch nicht detailiert gesucht ... hab erstmal mit PowerShell, CommandLine/CMD und WSL gespielt und halb fertig) Ob es diese Speicherlecks des Python auch m WSH gibt, weiß ich nicht ... wie gesagt, die Installation davon funktioniert aktuell nicht mehr und selbst das Python dort zu registrieren hab ich noch nicht hinbekommen (und falls doch, dann würde ich dort "meine" Speicherlecks natürlich mitbringen). 3 bis 12 oder mehr MB pro Ausführung, klingt nicht viel, aber 100 Mal gemacht, ist ein 32 Bit-Prozess im Arsch. ![]() |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:27 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz