Jump to content

pyscripter

Members
  • Content Count

    1045
  • Joined

  • Last visited

  • Days Won

    70

Everything posted by pyscripter

  1. Whilst there are many Delphi components for detecting changes to file system folders they suffer from serious limitations: typically they only allow you to monitor a single folder they do not support the monitoring of specific files they rely on the FindFirstChangeNotification API which gives no information about what has changed, requiring an inefficient search. I have created a new file system monitoring library that addresses the above limitations. Features: Easy to use, but also suitable for heavy duty monitoring Single unit with no external dependencies Allows monitoring folders and/or specific files Uses the ReadDirectoryChangesW API which provides information about what exactly was changed A single instance of the component can handle the monitoring of many folders and/or files Uses an I/O completion port for efficient handling of large numbers of requests A single thread handles all requests A different notification handler can be specified for each request You can have multiple handlers for each folder or file When you monitor folders you can specify whether you want to also monitor subfolders Installation: You do not need to install the library. Just download or clone the repo and add the source subdirectory to the Library path. Usage: procedure TForm1.FormCreate(Sender: TObject); begin // Create the IFileSystemMonitor interface FFileSystemMonitor := CreateFileSystemMonitor; // Monitor a directory FFileSystemMonitor.AddDirectory(TPath.GetTempPath, False, HandleChange); // Also monitor a specific file FFileSystemMonitor.AddFile('pathtoyourfile', HandleChange); end; procedure TForm1.HandleChange(Sender: TObject; const Path: string; ChangeType: TFileChangeType); begin with lvEventList.Items.Add do begin Caption := GetEnumName(TypeInfo(TFileChangeType), Integer(ChangeType)); SubItems.Add(Path); end; end; To stop monitoring a specific file or folder you use the following methods: function RemoveDirectory(const Directory: string; OnChange: TMonitorChangeHandler): Boolean; function RemoveFile(const FilePath: string; OnChange: TMonitorChangeHandler): Boolean;
  2. pyscripter

    New file system monitoring component

    You will still have the same issue if the parent directory (or the parent of the parent...) gets deleted or renamed. Even if not, you may be getting many more notifications than you need. Also you would have to watch subfolders, which you may not want. It is not worth it. Checking every x seconds whether the monitored directories still exist, is a tiny overhead.
  3. pyscripter

    New file system monitoring component

    @mjustin Have a look at the source code. https://github.com/pyscripter/FileSystemMonitor/blob/2dd27a0570ae2f839acb9de3bf67bb2247893784/Source/FileSystemMonitor.pas#L415C16-L415C41 All the thread does is wait on the I/O complection port using GetQueuedCompletionStatus. This function blocks until a change happens. The only polling that takes place is to check every 5 seconds, for deletion/rename of the monitored directories themselves, which are not reported by ReadDirectoryChanges. Folders can be attached to/detached from the completion port at any time, without any problem.
  4. pyscripter

    Correctly let python free a result

    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.
  5. pyscripter

    Correctly let python free a result

    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.
  6. pyscripter

    Correctly let python free a result

    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.
  7. pyscripter

    Correctly let python free a result

    Please search this forum for answers/discussion of this question.
  8. pyscripter

    Correctly let python free a result

    Could you please attach a zipped project instead?
  9. pyscripter

    Correctly let python free a result

    It will be called if you change your python script to: entry = manager.GetNewEntry() print(entry.Name()) entry = None
  10. pyscripter

    Correctly let python free a result

    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.
  11. pyscripter

    Stopping a P4D Python thread

    TPythonThread is a TThread descendent. But TThread.Terminate, does not stop threads. Your code needs to check the value of Terminated. Did you check Demo 33?https://github.com/pyscripter/python4delphi/blob/b6b63c7a2e94e027a52eac340d6fb38f084d5269/Demos/Demo33/SortThds.pas#L147C1-L158C5. This works in that case because the python code releases the GIL regularly (when you call swap). Alternatively you can expose to your python scripts a Delphi variable that the python scripts check at short intervals. In other words you have to engineer a way to stop the threads gracefully. The same applies to standard Delphi threads.
  12. pyscripter

    Can't print from Python script thread to Delphi main thread

    @Martybartfast Support for output redirection from sub-interpreters has been committed. So with the latest version you do need the above workaround.
  13. pyscripter

    Can't print from Python script thread to Delphi main thread

    @Martybartfast Actually there is an easy solution: Replace TPyThread.ExecuteWithPython with procedure TPyThread.ExecuteWithPython; // Run the script begin TPythonModule(PyEngine.IOPythonModule).MultInterpretersSupport := mmiPerInterpreterGIL; TPythonModule(PyEngine.IOPythonModule).InitializeForNewInterpreter; PyEngine.DoRedirectIO; PyEngine.ExecString(FScript); // Run Python script end; and printing should work.
  14. pyscripter

    Can't print from Python script thread to Delphi main thread

    Output redirection is not supported with emNewInterpreterOwnGIL. See other threads with a discussion of the limitations of that mode in this forum.
  15. From https://github.com/pyscripter/python4delphi/wiki/PythonThreads Why do the following inside the thread? GetPythonEngine.IO := PythonInputOutput1; // do these assignments go here, or before the ThreadPythonExec GetPythonEngine.FatalMsgDlg := FALSE; GetPythonEngine.FatalAbort := TRUE; GetPythonEngine.Py_FatalError := @MyFatalMessage;
  16. pyscripter

    TPyDelphiWrapper and initialization/finalization issue

    Yes you can.
  17. pyscripter

    TPyDelphiWrapper and initialization/finalization issue

    There is no need to create and destroy TPyDelphiWrapper. Just create it once with the engine. It will be automatically initialized when the python dll is loaded.
  18. pyscripter

    PythonEngine.PyUnicode_FromString() issue

    P4D provides a function: function TPythonEngine.PyUnicodeFromString(const AString : UnicodeString) : PPyObject;
  19. Yes but note that PyObject_SetAttrString increments the reference count of pObj. So if you want to pass the responsibility of destroying the object to python, you need to decrement it: fPyEngine.Py_DecRef(pObj);
  20. Actually for this use case the TPythonModule is not needed. You can directly import the variables into the '__main__' module. e,g, procedure TForm1.Button1Click(Sender: TObject); var PyEngine: TPythonEngine; PyHeight: Integer; PyIO: TPythonGUIInputOutput; PyWidth: Integer; VarHeight: TPythonDelphiVar; VarWidth: TPythonDelphiVar; begin PyWidth := 80; PyHeight := 40; PyEngine := TPythonEngine.Create(nil); PyIO := TPythonGUIInputOutput.Create(nil); try PyIO.Output := Memo1; PyEngine.IO := PyIO; PyEngine.UseLastKnownVersion := True; PyEngine.AutoLoad := False; PyEngine.AutoUnload := False; try PyEngine.LoadDll; except on E: Exception do begin ShowMessage('Error at load Python: ' + E.Message); Exit; end; end; VarWidth := TPythonDelphiVar.Create(nil); VarWidth.Engine := PyEngine; VarWidth.Module := '__main__'; VarWidth.VarName := 'WIDTH'; VarWidth.Initialize; VarWidth.Value := PyWidth; VarHeight := TPythonDelphiVar.Create(nil); VarHeight.Engine := PyEngine; VarHeight.Module := '__main__'; VarHeight.VarName := 'HEIGHT'; VarHeight.Initialize; VarHeight.Value := PyHeight; var Source := ''' import math result = sum(i * i for i in range(1, 11)) print("Suma kwadratów od 1 do 10 to:", result) canvas = [[' ' for _ in range(WIDTH.Value)] for _ in range(HEIGHT.Value)] print(canvas) '''; PyEngine.ExecString(Utf8Encode(Source), '__main__'); finally VarWidth.Free; VarHeight.Free; PyIO.Free; PyEngine.Free; end; end;
  21. Here the following runs fine (without importing builtins): procedure TForm1.Button1Click(Sender: TObject); var PyEngine: TPythonEngine; PyHeight: Integer; PyIO: TPythonGUIInputOutput; PyModule: TPythonModule; PyWidth: Integer; VarHeight: TPythonDelphiVar; VarWidth: TPythonDelphiVar; begin PyWidth := 80; PyHeight := 40; PyEngine := TPythonEngine.Create(nil); PyModule := TPythonModule.Create(nil); PyIO := TPythonGUIInputOutput.Create(nil); try PyIO.Output := Memo1; PyEngine.IO := PyIO; PyEngine.UseLastKnownVersion := True; PyEngine.AutoLoad := False; PyEngine.AutoUnload := False; PyModule.Engine := PyEngine; PyModule.ModuleName := '__main__'; try PyEngine.LoadDll; except on E: Exception do begin ShowMessage('Error at load Python: ' + E.Message); Exit; end; end; PyModule.Initialize; //PyEngine.PyRun_SimpleString('import builtins; __builtins__ = builtins'); VarWidth := TPythonDelphiVar.Create(nil); VarWidth.Engine := PyEngine; VarWidth.Module := '__main__'; VarWidth.VarName := 'WIDTH'; VarWidth.Initialize; VarWidth.Value := PyWidth; VarHeight := TPythonDelphiVar.Create(nil); VarHeight.Engine := PyEngine; VarHeight.Module := '__main__'; VarHeight.VarName := 'HEIGHT'; VarHeight.Initialize; VarHeight.Value := PyHeight; var Source := ''' import math result = sum(i * i for i in range(1, 11)) print("Suma kwadratów od 1 do 10 to:", result) canvas = [[' ' for _ in range(WIDTH.Value)] for _ in range(HEIGHT.Value)] print(canvas) '''; PyEngine.ExecString(Utf8Encode(Source), '__main__'); finally VarWidth.Free; VarHeight.Free; PyIO.Free; PyModule.Free; PyEngine.Free; end; end; However you should not call the module '__main__', since by doing that you will be overriding the default python module with the same name. Call the module something else say my_module and then add to your python script: from my_module import WIDTH, HEIGHT
  22. The above should also be: HEIGHT.Value = 40 WIDTH.Value = 60 You should not need to import builtins. Not sure what the problem was.
  23. canvas = [[' ' for _ in range(WIDTH)] for _ in range(HEIGHT)] needs to be canvas = [[' ' for _ in range(WIDTH.Value)] for _ in range(HEIGHT.Value)]
  24. Does reversing the order help? VarWidth.Value := PyWidth; VarWidth.Initialize;
  25. I am confused. You are talking about your own python file (CustomClasses), but also something you installed via pip. For python to find your file it needs to be either on the python path or on the current directory. Run the following using P4D: import sys import os print(sys.path) print(os.getcwd()) print(sys.executable) and check the values of the path and the current directory. Also check which python version you are using.
×