PythonNET 3.x is "Fixed"

  • 23 March 2023
  • 5 replies

Userlevel 6
Badge +2

Hi Everyone (+ @Allie & @Sandrine Auriol),

There is nothing inherently wrong with the modern version of PythonNET but there were some changes that the PythonNET team made to inheritance.  PythonNET is now more in line with C# where you have to use the proper static variable type (my best guess is this is better handle multi-threading and easier integration of Python into C# using the GIL...the opposite of what we’re trying to do with the ZOS-API).

In PythonNET 3.x, when a method returns a interface with inheritance, the returned variable becomes a type of the parent interface (not the child interface).  For example, if you use the GetSettings() method for an analysis window, all settings have IAS_ parent interface and a child interface with the actual settings for the analysis window.  

In previous versions of PythonNET, the returned variable was dynamically typed and PythonNET attempted an implicit conversion from the parent interface to the child interface.  So the following lines of code were sufficient to get the analysis settings:

win = TheSystem.Analyses.New_GeometricImageAnalysis()
ws1 = win.GetSettings()

The GetSettings() method is implemented on the IAS_ interface but ws1 implicitly became a type of IAS_GeometricImageAnalysis.  This changed in PythonNET 3.x and now you need to explicitly convert from IAS_ to IAS_GeometricImageAnalysis:

win = TheSystem.Analyses.New_GeometricImageAnalysis()
ws1 = win.GetSettings()
ws2 = ZOSAPI.Analysis.Settings.ExtendedScene.IAS_GeometricImageAnalysis(ws1)

Notice how you have to explicitly convert the GetSettings() to the new interface on line 3 (this can be combined into 1 line if desired).

This update also works for IBatchRayTrace with System.Int32/System.Double out/ref parameters.

The attached Python file has 2 tests (GeometricImageAnalysis and IZRDReader).  

PythonNET 3.x also improved the garbage collection as well which actually breaks the ability to pass CLR classes between different methods.  So you cannot successfully pass self.TheApplication.CloseApplication() in the __del__ method for the PythonStandaloneApplication().  It appears with some basic testing that simply calling self.TheApplication = None is enough in the __del__ method, but I would have an engineer/developer spend some time confirming this.

Zemax should update both the StandaloneApplication/IteractiveExtension templates as well as the sample files to reflect these changes.

all tests done with PythonNET 3.0.1 & Python 3.8.10

5 replies

Userlevel 6
Badge +2

Hi @MichaelH ! Thank you very much for making us aware of that. I will reach out to the product team.

Userlevel 7
Badge +2

Impressive @MichaelH, thank you for sharing this with us! It was becoming more and more of a setback to be bound by Pythonnet 2.5.2. I’m looking forward to use Python 3.10 match case statement in my ZOS-API codes.

Take care,


Userlevel 6
Badge +2

As a follow up which might be easier for users to upgrade their code, besides explicit casting:

ws1 = win.GetSettings()
ws2 = ZOSAPI.Analysis.Settings.ExtendedScene.IAS_GeometricImageAnalysis(ws1)

there is also implicit casting in PythonNET 3.x:

ws1 = win.GetSettings().__implementation__


ws1 = win.GetSettings().__raw_implementation__

The implicit casting has a more Pythonic syntax but might be easier for some cases.  I have only tested this on the GetSettings() method but the __implementation__ and __raw_implementation__ should work for all cases.

Working with interfaces · pythonnet/pythonnet Wiki (

Userlevel 6
Badge +2

Wow thanks Michael! And thanks for tagging me. I’m a little bummed to see that casting is back to be honest, but this is useful information to have as we create more dynamic content using PyAnsys. I appreciate your walk-through here :).

Edit: I made this a featured topic so others can see this. Thanks again for a very informative post Michael :)

Userlevel 2

There is now an alternative solution which preserves the original interface:

The ZOSPy library wraps the Zemax IA_ objects in a class that internally performs implicit casting using the __implementation__ property.

The example from the first post would then become:

import zospy as zp

# ... connection setup ...

analysis = zp.analyses.new_analysis(oss, zp.constants.Analysis.AnalysisIDM.GeometricImageAnalysis)