bastibe / transplant

Transplant is an easy way of calling Matlab from Python
https://transplant.readthedocs.io
Other
108 stars 26 forks source link

Cannot set attributes #96

Open paulbareilCodotek opened 2 years ago

paulbareilCodotek commented 2 years ago

Hi, I cannot set attributes if the class does not import mixin. The following matlab TestObject is in a @TestObject folder and TestObject.m file. I can create an instance of this class in Matlab and change the attribute. I can do:

test = TestObject(2); and if I print test.attribute, it returns 2. If I then do test.attribute = 3;, it will change its value.

However, doing this in Python does not work. The values stays at 2. If I change the Matlab class header to classdef TestObject < matlab.mixin.Copyable, then it works. However, I need to interface to matlab code that does not implement the mixin class. How can I change the attribute of the Matlab class then?

`classdef TestObject properties

    attribute        = 12345;

end

methods

    function obj = set.attribute(obj, value)
        obj.attribute = value;
    end
    function obj = TestObject(value)

        if nargin == 1
            obj.attribute = value;

        else
            error('Provide value')
        end
    end

end

end`

bastibe commented 2 years ago

Honestly, I have no idea. Transplant doesn't do anything crazy, though: It calls subsasgn on the object, nothing more.

Does your class work with subsasgn?

paulbareilCodotek commented 2 years ago

I was able to debug the Python and Matlab code. I haven't found the exact source of the problem yet, still trying to wrap my head around the way it works. But from what I understand, the problem is the 'del proxy'

The python code below calls matlab with the substruct function to get the structure necessary to make the call, then the subsasgn makes the call correctly and somewhere in the process, on the Matlab side, a new proxied_objects object is created at the last cell array index.

access = MatlabStruct(self.process.substruct('.', name)) self.process.subsasgn(self, access, value)

That new proxied_object is correct. It has the changed value. However, when the following function is called, the object we just created and modified is deleted.

def __del__(self): self.process._del_proxy(self.handle)

I do not know yet why the del function is called and why a new proxied_objects is created instead of being modified, but the problem lies there. It also explains why a copyable mixin class has no problem since the copy is shallow.

paulbareilCodotek commented 2 years ago

Got it. The self.process.subsasgn(self, access, value) line actually returns the new modified object. As in the Matlab object we have to return the object itself.

function obj = set.attribute(obj, value)

        `obj.attribute = value;

    end`

While, when subclassing < matlab.mixin.Copyable there is a warning saying "Set function in handle class does not need to return the modified object."

So the actual modified object is discarded right now, but when trying this directly in Matlab it seems to automatically reapply the object. It is actually a big problem because if I create an object testObject = TestObject() and then re-use that object accross different other objects, none of them will see the change because a different copy would have been created.

Do you see any way to go around this? Other than < matlab.mixin.Copyable I mean?

bastibe commented 2 years ago

Thank you for your analysis! That sounds like a nasty problem. If I understand you correctly, Copyable classes behave differently than non-Copyable classes to subsasgn.

I think we would have to (conditionally?) overwrite self.process in __setattr__. Can we do that in every case, or only for one of the Copyable/non-Copyable?

In either case, I'm afraid I don't have access to Matlab any longer, which means I can't test any changes myself. But I'll happily review and accept a pull request!

paulbareilCodotek commented 2 years ago

Yes that seems to be the case. I will investigate a little further on the Matlab side to be sure to understand the problem correctly. You built such a good package, I feel like I have to contribute a little if I can. I do not have much time, but will come back on this.

paulbareilCodotek commented 2 years ago

ok, I made some quick Matlab tests If you have the following class in Matlab

`classdef TestObject

properties
    attribute = 123456;
    ref_obj;
end
methods
    function obj = set.attribute(obj, value)
        obj.attribute = value
    end
    function obj = set.ref_obj(obj, value)
        obj.ref_obj = value
    end
    function obj = TestObject(value)
        obj.attribute = value;
    end
end

end`

Then if you create 2 objects and reference it on the second object

test = TestObject(1); test2 = TestObject(2); test2.ref_obj = test;

If you do test.attribute = 10; test2.ref_obj will return 1

!!!. So the problem is NOT transplant.

It seems Matlab "standard" classes cannot be referenced! Making it inherit copy was not a requirement, it need at least to inherit from "handle" to work as a referenceable class. https://www.mathworks.com/help/matlab/handle-classes.html

bastibe commented 2 years ago

Oh, Matlab! I do wonder what considerations and constraints have lead to the maze of curveballs that is the Matlab object system.

At any rate, thank you for sharing your analysis. I find it oddly satisfying to dive deeply into strange code such as Matlab's.