hanjinliu / magic-class

Generate multifunctional and macro recordable GUIs from Python classes
https://hanjinliu.github.io/magic-class/
BSD 3-Clause "New" or "Revised" License
40 stars 5 forks source link

`Error: 'NoneType' object has no attribute '__magicclass_parent__'` with a common parent class #113

Closed multimeric closed 1 year ago

multimeric commented 1 year ago

Following the docs on inheriting magicclasses, I tried to factor a common field out into a parent class, with its own self-contained logic using @connect. However, I've identified that, if you have two such classes that use the same parent class, you get an unusual fatal error:

from magicclass import magicclass, vfield, MagicTemplate

class Parent(MagicTemplate):
    foo = vfield(False)

    @foo.connect
    def _foo_changed(self) -> None:
        print("Foo changed")

@magicclass
class Container:
    @magicclass
    class ChildA(Parent):
        bar = vfield(int)

    @magicclass
    class ChildB(Parent):
        bar = vfield(int)

Container().show()

When you check one of the foo boxes, you get:

Traceback (most recent call last):
  File "src/psygnal/_signal.py", line 980, in _run_emit_loop
  File "src/psygnal/_weak_callback.py", line 288, in cb
  File "/opt/anaconda3/lib/python3.9/site-packages/magicclass/fields/_define.py", line 40, in _callback
    current_self = current_self.__magicclass_parent__
AttributeError: 'NoneType' object has no attribute '__magicclass_parent__'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/opt/anaconda3/lib/python3.9/site-packages/magicgui/widgets/bases/_value_widget.py", line 65, in _on_value_change
    self.changed.emit(value)
  File "src/psygnal/_signal.py", line 935, in emit
  File "src/psygnal/_signal.py", line 982, in _run_emit_loop
psygnal._exceptions.EmitLoopError: calling <psygnal._weak_callback._StrongFunction object at 0x7fa2b9d00640> with args=(True,) caused AttributeError: 'NoneType' object has no attribute '__magicclass_parent__'.
multimeric commented 1 year ago

Actually this seems to happen in any scenario where the event handler function is re-used across multiple magic classes, even when inheritance isn't involved:

from magicclass import magicclass, vfield, MagicTemplate

def _foo_changed(self) -> None:
    print("Foo changed")

@magicclass
class Container:
    @magicclass
    class ChildA:
        foo = vfield(int)
        _ = foo.connect(_foo_changed)

    @magicclass
    class ChildB:
        foo = vfield(int)
        _ = foo.connect(_foo_changed)

Container().show()
hanjinliu commented 1 year ago

Thank you @multimeric ! That might be a result of an incomplete parent-search strategy... I think I can include this bug fix in the upcoming next release in a week or so.

Note for me: Following bug might be relevant to this bug.


def _foo_changed(self, w=None) -> None:
    print("Foo changed")

@magicclass
class ChildA:
    foo = vfield(int)
    foo.connect(_foo_changed)
a = ChildA()
a.show()