marcelsema 0 Posted Tuesday at 07:17 AM Hello everyone, I am looking to destroy an object once it is no longer referenced in python. I am struggeling to correctly do this. unit Unit1; interface uses SysUtils, Classes, Windows, Messages, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, PythonEngine, Vcl.PythonGUIInputOutput, WrapDelphi; type TForm1 = class(TForm) Splitter1: TSplitter; Memo1: TMemo; PythonEngine1: TPythonEngine; PythonModule1: TPythonModule; Panel1: TPanel; Button1: TButton; PythonGUIInputOutput1: TPythonGUIInputOutput; Memo2: TMemo; PyDelphiWrapper1: TPyDelphiWrapper; procedure Button1Click(Sender: TObject); private public destructor Destroy; override; function GetDelphiWrapper: TPyDelphiWrapper; function GetPythonEngine: TPythonEngine; end; TEntry = class(TObject) strict private FstrName: string; FclsStringList: TStringList; public constructor Create(strName: string); destructor Destory; function Name: string; end; TManager = class(TObject) public function GetNewEntry: PPyObject; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var clsManager: TManager; pyManager: PPyObject; begin clsManager := TManager.Create; pyManager := PyDelphiWrapper1.Wrap(clsManager, soReference); PythonModule1.SetVar( 'manager', pyManager ); PythonEngine1.ExecStrings( memo1.Lines ); PythonEngine1.Py_DecRef(pyManager); end; { TManager } function TManager.GetNewEntry: PPyObject; begin // I want TEntry to be destroyed by python once it is no longer used Result := Form1.GetDelphiWrapper.Wrap(TEntry.Create('Test'), soOwned); Form1.GetPythonEngine.Py_DECREF(Result); end; destructor TForm1.Destroy; begin FreeAndNil(PythonModule1); FreeAndNil(PythonEngine1); inherited; end; function TForm1.GetDelphiWrapper: TPyDelphiWrapper; begin Result := PyDelphiWrapper1; end; function TForm1.GetPythonEngine: TPythonEngine; begin Result := PythonEngine1; end; { TEntry } constructor TEntry.Create(strName: string); begin inherited Create; FclsStringList := TStringList.Create; FstrName := strName; end; destructor TEntry.Destory; begin FreeAndNil(FclsStringList); inherited; end; function TEntry.Name: string; begin Result := FstrName; end; end. from spam import * entry = manager.GetNewEntry() print(entry.Name()) After clicking the button, I get an exception accessing an invalid pointer. I also tried returning the object directly instead of wraping it to a PPyObject, but that did not work. Thank you for your help. Best regards, Marcel Share this post Link to post
pyscripter 828 Posted Tuesday at 10:43 AM (edited) function TManager.GetNewEntry: PPyObject; begin // I want TEntry to be destroyed by python once it is no longer used Result := Form1.GetDelphiWrapper.Wrap(TEntry.Create('Test'), soOwned); Form1.GetPythonEngine.Py_DECREF(Result); end; In the code above Py_DECREF will destroy the TEntry instance immediately, You can confirm that by debugging and putting a brekpoint at TEntry.Destroy. Just remove the Py_DECREF statement. Explanation.: Wrap returns a python object with reference count of 1. When you decrease the reference count, it goes down to 0, and because the wrapped Delphi object is owned it gets destroyed The difference with clsManager := TManager.Create; pyManager := PyDelphiWrapper1.Wrap(clsManager, soReference); PythonModule1.SetVar( 'manager', pyManager ); PythonEngine1.ExecStrings( memo1.Lines ); PythonEngine1.Py_DecRef(pyManager); is that you call SetVar which increases the reference count. However the above code leaks clsManager. It does not get destroyed because you set the Ownership to soReference. Either change that or call clsManager.Free at the end. Conclusion: Python reference counting can be tricky. You should try to understand the rules behind reference counting. (e.g. Reference Counting in Python (tripod.com)) if you use the Python API directly. High level P4D modules (WrapDelphi and VarPython) to a large extent insulate you from that and ensure correct use of reference counting. Edited Tuesday at 11:37 AM by pyscripter Share this post Link to post
marcelsema 0 Posted Wednesday at 05:48 AM Hey, Thank you for your answer. The problem why I included Form1.GetPythonEngine.Py_DECREF(Result); is that TEntry.Destory is otherwise never called and I just want TEntry destroyed after I executed the script. You are right, clsManager gets leaked. I assume that I have to look into DEMO32 for the correct way to use the WrapDelphi ? Best regards, Marcel Share this post Link to post
pyscripter 828 Posted Wednesday at 09:10 AM (edited) 3 hours ago, marcelsema said: TEntry.Destory is otherwise never called It will be called if you change your python script to: entry = manager.GetNewEntry() print(entry.Name()) entry = None Edited Wednesday at 09:12 AM by pyscripter Share this post Link to post
marcelsema 0 Posted Wednesday at 09:44 AM (edited) Interesting. I am unable to get this to work. I adjusted the python script, removed the Py_DECREF and no destructor is called. Is it also possible to clean up variables after the ExecStrings? unit Unit1; interface uses SysUtils, Classes, Windows, Messages, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, ComCtrls, PythonEngine, Vcl.PythonGUIInputOutput, WrapDelphi; type TForm1 = class(TForm) Splitter1: TSplitter; Memo1: TMemo; PythonEngine1: TPythonEngine; PythonModule1: TPythonModule; Panel1: TPanel; Button1: TButton; PythonGUIInputOutput1: TPythonGUIInputOutput; Memo2: TMemo; PyDelphiWrapper1: TPyDelphiWrapper; procedure Button1Click(Sender: TObject); private public destructor Destroy; override; function GetDelphiWrapper: TPyDelphiWrapper; function GetPythonEngine: TPythonEngine; end; TEntry = class(TObject) strict private FstrName: string; FclsStringList: TStringList; public constructor Create(strName: string); destructor Destory; function Name: string; end; TManager = class(TObject) public function GetNewEntry: PPyObject; end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var clsManager: TManager; pyManager: PPyObject; begin try clsManager := TManager.Create; pyManager := PyDelphiWrapper1.Wrap(clsManager, soReference); PythonModule1.SetVar( 'manager', pyManager ); PythonEngine1.ExecStrings( memo1.Lines ); PythonEngine1.Py_DecRef(pyManager); finally FreeAndNil(clsManager); end; end; { TManager } function TManager.GetNewEntry: PPyObject; begin // I want TEntry to be destroyed by python once it is no longer used Result := Form1.GetDelphiWrapper.Wrap(TEntry.Create('Test'), soOwned); //Form1.GetPythonEngine.Py_DECREF(Result); end; destructor TForm1.Destroy; begin FreeAndNil(PythonModule1); FreeAndNil(PythonEngine1); inherited; end; function TForm1.GetDelphiWrapper: TPyDelphiWrapper; begin Result := PyDelphiWrapper1; end; function TForm1.GetPythonEngine: TPythonEngine; begin Result := PythonEngine1; end; { TEntry } constructor TEntry.Create(strName: string); begin inherited Create; FclsStringList := TStringList.Create; FstrName := strName; end; destructor TEntry.Destory; begin FreeAndNil(FclsStringList); inherited; end; function TEntry.Name: string; begin Result := FstrName; end; end. object Form1: TForm1 Left = 214 Top = 174 Width = 592 Height = 422 VertScrollBar.Range = 210 ActiveControl = Memo1 Caption = 'Form1' Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = 11 Font.Name = 'MS Sans Serif' Font.Pitch = fpVariable Font.Style = [] TextHeight = 13 object Splitter1: TSplitter Left = 0 Top = 113 Width = 576 Height = 3 Cursor = crVSplit Align = alTop ExplicitTop = 169 end object Memo1: TMemo Left = 0 Top = 116 Width = 576 Height = 226 Align = alClient Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 Font.Name = 'Consolas' Font.Pitch = fpVariable Font.Style = [] Lines.Strings = ( 'from spam import *' '' 'entry = manager.GetNewEntry()' 'print(entry.Name())' 'entry = None') ParentFont = False ScrollBars = ssVertical TabOrder = 0 WordWrap = False ExplicitWidth = 570 ExplicitHeight = 209 end object Panel1: TPanel Left = 0 Top = 342 Width = 576 Height = 41 Align = alBottom BevelOuter = bvNone TabOrder = 1 ExplicitTop = 325 ExplicitWidth = 570 object Button1: TButton Left = 8 Top = 8 Width = 75 Height = 25 Caption = 'Execute' TabOrder = 0 OnClick = Button1Click end end object Memo2: TMemo Left = 0 Top = 0 Width = 576 Height = 113 Align = alTop Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 Font.Name = 'Consolas' Font.Pitch = fpVariable Font.Style = [] ParentFont = False TabOrder = 2 ExplicitWidth = 570 end object PythonEngine1: TPythonEngine DllPath = 'D:\temp\PythonPkg' IO = PythonGUIInputOutput1 Left = 48 Top = 24 end object PythonModule1: TPythonModule Engine = PythonEngine1 ModuleName = 'spam' Errors = < item Name = 'PointError' ErrorType = etClass end item Name = 'EBadPoint' ErrorType = etClass ParentClass.Name = 'PointError' end> Left = 152 Top = 24 end object PythonGUIInputOutput1: TPythonGUIInputOutput UnicodeIO = True RawOutput = False Output = Memo2 Left = 376 Top = 24 end object PyDelphiWrapper1: TPyDelphiWrapper Engine = PythonEngine1 Module = PythonModule1 Left = 248 Top = 24 end end Edited Wednesday at 09:58 AM by marcelsema Share this post Link to post
pyscripter 828 Posted Wednesday at 10:07 AM Could you please attach a zipped project instead? Share this post Link to post
pyscripter 828 Posted Wednesday at 10:20 AM 36 minutes ago, marcelsema said: Is it also possible to clean up variables after the ExecStrings? Please search this forum for answers/discussion of this question. Share this post Link to post
marcelsema 0 Posted Wednesday at 10:44 AM 37 minutes ago, pyscripter said: Could you please attach a zipped project instead? Unit1.zip Thank you for taking a look at it. Share this post Link to post
pyscripter 828 Posted Wednesday at 11:38 AM (edited) TEntry = class(TObject) strict private FstrName: string; FclsStringList: TStringList; public constructor Create(strName: string); destructor Destory; function Name: string; end; Destroy misspelled and not declared with override. If you fix this TEntry gets destroyed. But the better way is: function TManager.GetNewEntry: TEntry; begin Result := TEntry.Create('Test'); end; and in python from spam import * entry = manager.GetNewEntry() print(entry.Name()) entry.__owned__ = True entry = None Explanation: When P4D wraps the result of GetNewEntry it does not know who owns the return value. The default is that Delphi does. But the ownership can be changed in python using the __owned__ property. Edited Wednesday at 11:45 AM by pyscripter 1 Share this post Link to post
marcelsema 0 Posted Wednesday at 11:47 AM omg I am so sorry -.- Thx for spotting the issue. 1 hour ago, pyscripter said: Please search this forum for answers/discussion of this question. I assume https://github.com/pyscripter/python4delphi/discussions/455#discussioncomment-7974673 is the solution for this. Thank you very much for your time! Best regards, Marcel Share this post Link to post
pyscripter 828 Posted Wednesday at 11:54 AM 3 minutes ago, marcelsema said: I assume https://github.com/pyscripter/python4delphi/discussions/455#discussioncomment-7974673 is the solution for this. Not really. You can use the following ExecStrings overload procedure ExecStrings(strings: TStrings; locals, globals: PPyObject; const FileName: string = '<string>'); overload; providing your own globals and locals so that you do not mess up the python namespace. Please search in this forum. There was a discussion about this. Share this post Link to post
pyscripter 828 Posted Wednesday at 12:20 PM (edited) For the benefit of anyone reading this thread, the most powerful way to allow your python script to create instances of a Delphi class (e.g. TEntry) is to create a wrapper: type TEntryClassWrapper = class(TPyClassWrapper<TEntry>) constructor CreateWith(APythonType: TPythonType; args, kwds: PPyObject); overload; override; end; { TEntryClassWrapper } constructor TEntryClassWrapper.CreateWith(APythonType: TPythonType; args, kwds: PPyObject); var _obj : PPyObject; begin if GetPythonEngine.PyArg_ParseTuple( args, 'O:CreteWith',@_obj ) <> 0 then begin Create(APythonType); DelphiObject := TEntry.Create(GetPythonEngine.PyObjectAsString(_obj)); end; end; You need to register and initialize the wrapper in your Form.Create: procedure TForm1.FormCreate(Sender: TObject); begin PyDelphiWrapper1.RegisterDelphiWrapper(TEntryClassWrapper).Initialize; end; and then in python: from spam import * entry = Entry('Test') print(entry.Name()) entry = None The Delphi TEntry class is exposed as the python type Entry in your python code. Edited Wednesday at 12:29 PM by pyscripter 1 Share this post Link to post