Parameter observer requires a global variable?

Need help, or want to share a macro? Post here!
Post Reply
User avatar
Roy_043
Veteran
Posts: 7678
Joined: Thu Dec 27, 2018 12:28 pm

Parameter observer requires a global variable?

Post by Roy_043 »

I am trying to use a parameter group observer. For some reason the parameter group needs to be a global variable. It took me some time :D to discover this. The code below does not work if param is a local variable in _start_observer. Why is that?

Code: Select all

class _ParamObserver:

    def slotParamChanged(self, param, tp, name, value):
        print("slotParamChanged")
        print(param)
        print(tp)
        print(name)
        print(value)

param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")

def _start_observer():
    global param
    param.AttachManager(_ParamObserver())

_start_observer()
bdm
Posts: 139
Joined: Sat Dec 31, 2022 12:10 pm

Re: Parameter observer requires a global variable?

Post by bdm »

Roy_043 wrote: Sun Nov 19, 2023 9:55 pm Why is that?
As far as I can tell, the parameter group (variable "param") does not have to be global, but it has to be kept alive in memory.
As soon as "param" is destroyed by the garbage collector, the observer gets detached.
see https://github.com/FreeCAD/FreeCAD/blob ... #L252-L260

You don't need a "global" keyword. Just assign "param" to something thas is kept in memory anyways.

Two examples that will work without the "global" keyword:

Code: Select all

class _ParamObserver:

    def slotParamChanged(self, param, tp, name, value):
        print("slotParamChanged")
        print(param)


def _start_observer():
    param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
    param.AttachManager(_ParamObserver())
    return param

param = _start_observer()  # keep "param" alive

Code: Select all

class _ParamObserver:

    def slotParamChanged(self, param, tp, name, value):
        print("slotParamChanged")
        print(param)


def _start_observer(ll=[]):
    param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
    param.AttachManager(_ParamObserver())
    ll.append(param)  # keep "param" alive

_start_observer()
TheMarkster
Veteran
Posts: 5313
Joined: Thu Apr 05, 2018 1:53 am

Re: Parameter observer requires a global variable?

Post by TheMarkster »

The global keyword signifies to python that the variable is able to be modified within the function.

Code: Select all

var1 = "Var1"

def changeVar1():
    var1 = "Something else"

def changeVar1GlobalVersion():
    global var1
    var1 = "Something else"


print(f"var1 = {var1}")
changeVar1()
print(f"var1 after running changeVar1(): {var1}")
changeVar1GlobalVersion()
print(f"var1 after running changeVar1GlobalVersion(): {var1}")

18:31:23 var1 = Var1
18:31:23 var1 after running changeVar1(): Var1
18:31:23 var1 after running changeVar1GlobalVersion(): Something else
User avatar
Roy_043
Veteran
Posts: 7678
Joined: Thu Dec 27, 2018 12:28 pm

Re: Parameter observer requires a global variable?

Post by Roy_043 »

Thanks.

Why does the code below work without these measures? The mdi variable is local to the function.

Code: Select all

    _view_observer_active = False

    def _view_observer_start():
        mw = FreeCADGui.getMainWindow()
        mdi = mw.findChild(QtWidgets.QMdiArea)
        global _view_observer_active
        if not _view_observer_active:
            mdi.subWindowActivated.connect(_view_observer_callback)
            _view_observer_active = True
            _view_observer_callback(mdi.activeSubWindow())  # Trigger initial update.
https://github.com/FreeCAD/FreeCAD/blob ... 1802-L1811
bdm
Posts: 139
Joined: Sat Dec 31, 2022 12:10 pm

Re: Parameter observer requires a global variable?

Post by bdm »

Roy_043 wrote: Mon Nov 20, 2023 8:58 am Why does the code below work without these measures? The mdi variable is local to the function.
It is all about the objects themselves.
The code line "mdi = mw.findChild(QtWidgets.QMdiArea)" gives you a "reference" to an object that stays alive in memory.
Even if the variable name "mdi" is just local, the object will remain because it is also referred by other variables (and thus preserved from garbage collection).


Just execute this example code and you will understand:

Code: Select all

from PySide2 import QtWidgets

def myfunc1():
    mw = FreeCADGui.getMainWindow()
    mdi = mw.findChild(QtWidgets.QMdiArea)
    mdi.asdf = 'hello roy'

def myfunc2():
    mw = FreeCADGui.getMainWindow()
    mdi = mw.findChild(QtWidgets.QMdiArea)
    print(mdi.asdf)


myfunc1()
myfunc2()  # this will print "hello roy"
User avatar
Roy_043
Veteran
Posts: 7678
Joined: Thu Dec 27, 2018 12:28 pm

Re: Parameter observer requires a global variable?

Post by Roy_043 »

IMO it is not as obvious as you seem to suggest. After all, a parameter group is also an object that references something. And the parameter group does not suddenly disappear.
bdm
Posts: 139
Joined: Sat Dec 31, 2022 12:10 pm

Re: Parameter observer requires a global variable?

Post by bdm »

Roy_043 wrote: Mon Nov 20, 2023 9:24 pm And the parameter group does not suddenly disappear.
App.ParamGet (for the parameter group) does not return the same object when you call it multiple times.

Try this and you will see:

Code: Select all

ll = []
for i in range(10):
    param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
    ll.append(param)

print({id(a) for a in ll})  # these are 10 different ids --> different objects

##

from PySide2 import QtWidgets

ll = []
for i in range(10):
    mw = FreeCADGui.getMainWindow()
    mdi = mw.findChild(QtWidgets.QMdiArea)
    ll.append(mdi)

print({id(a) for a in ll})  # this is only a single id (always the same)
User avatar
Roy_043
Veteran
Posts: 7678
Joined: Thu Dec 27, 2018 12:28 pm

Re: Parameter observer requires a global variable?

Post by Roy_043 »

Right, I'll have to pay attention to that. Thanks again.
Post Reply