I also searched this as a general python question, but didn’t get suitable results for working-in-GH condition. Besides, if a sub process is to be used, I’d rather rewrite the code in python 3.
To understand what happens it is good to understand how GH utilizes Python.
Although I am not fully aware of the inner workings, here is what I understand. Basically when a Python block is fired up, the interpreter is initialized, after that it does not get reinitialized. Loading of libraries and creation of variables will all be done in the same instance of the interpreter. Passing around variables between blocks that are not of a typical type such as int or float etc. is done by reference. When a reference is received by a Python block, the interpreter knows what is contained in it because it referring to data within its own runtime. Now here is the thing, when using two different versions of Python, each interpreter does not know about each other’s runtime. When you are passing a reference, you are only passing a pointer to a chunk of memory that Python3.9 assumes is a Python3.9 object, but in your case it is not.
How to get around this? The easy way would be to convert the Python2.7 into something that is more universally recognized, such as strings floats ints, or a datastructure thereof. This is something that has also been implemented in Python itself, and is called serialization and in Python it is called pickling.
Whith pickling you can serialize a class into a dictionary, which you can then convert into a string:
import pickle
old_type_serialized = str(pickle.dump(Oldtype()))
In Python 3.9 you can then convert it back into a dictionary:
old_type_dict = dict(obj)
Now you can access the item from this dictionary. You could also try to unpickle this dictionary into a class that you can use, however depending on the classs definition, you might run into some compatibility issues between the two version of Python.
I did not test any of the code above, but I think in general this should work, otherwise I hope the explanation will give you some new leads to solve this issue.
Do not mix incompatible languages and do not misuse scripts. When you are in need to pass custom objects in between components, and a migration is considered as a complex task, then it is time for a plugin. You probably should have done so a long time ago. There are just advantages of going this step, and any doable workaround will create a nightmare project instead.
But I couldn’t achieve the same thing in grasshopper. Not even doing both pickling and unpickling in ironpython 2.7, and not even doing the entire process inside one component.
I now tried the code myself aswell I get the same error, presumably because the class definition is done in the script itself. I also had a look at the string generated by pickling, which is not as readable as would be useful (It’s not a dict as I thought).
Another suggestion I have is by using the dict property of the class. You can then transfer this dict to the other python environment (you first need to cast it to a string).
There is however no generic way to use this dict to initialize a new instance of the class. Depending on the class simply unpacking it will work, but more likely it will need some more programming to achieve the desired result. If you are lucky there may already be a robust option in the class to initialize or create an instance of the class using a dict.
In all cases I won’t go as far as saying you shouldn’t go for this approach, but there is no nice way to achieve what you asked for. As with anything you need to know your options and then choose wisely
Thank you so much for your inputs. I tried your steps mentioned beyond and got the same results.
Which is as expected, that when a definition with the same name can be found in the unpickling environment, this is what should happen. I’m not sure what is happening to the customized objects created by GHpython
I also checked the new cpython3.9, even pickling objects is not possible.
Maybe this is an issue, or maybe this is how grasshopper environment is designed to be like.
And yes, there are alternative methods, like the dict property you mentioned, or write a json file, or even call a sub-process. Maybe asking for a quick connection between languages is a wrong request to ask in GH environment.
But I would left the topic open in case others also came up with this idea.
FWIW: Pickling does have known security risks. As the Python docs say:
Warning
The pickle module is not secure. Only unpickle data you trust.
It is possible to construct malicious pickle data which will execute arbitrary code during unpickling. Never unpickle data that could have come from an untrusted source, or that could have been tampered with.
Consider signing data with hmac if you need to ensure that it has not been tampered with.
Safer serialization formats such as json may be more appropriate if you are processing untrusted data. See Comparison with json.
If you really needed to in a pinch (though probably not a good idea in the long run, as previously explained by the other posters) you can find the relevant data from the ‘old’ object in the .dict attribute of the Py3 input object, and use that to rebuild a new object:
note that you cannot access the .dict attribute though dot-notation, but have to use the getattr() function, due to the preceding . on the key’s name.
If you will be doing a lot of passing objects like this, and know the structure of the classes you are working with ahead of time, it would likely be a better idea to implement a custom serialization / de-serialization though, where you can control and validate any incoming data on the ‘new’ object.
Though again: a better, better idea is probably not to pass objects between interpreters like that in the first place, if you can avoid it.
Yes, I guess this is the most covenient method we can adopt at this stage.
The original demand was to:
Only change a few components using Math.NET library in ironpython 2.7, to cpython 3.9, so Numpy/Scipy can be used. To avoid installing Math.NET library locally. While keep other components still in ironpython 2.7 (As I don’t have time to update them all).
So I guess this passing-object demand is only temporary. In the end eveything would be in python 3.
(Though still not understanding why pickle doesn’t work for customized object in GH environmrnt, but nevermind I guess)
As far as I understand, the CPython script editor uses Python.Net to interop with RhinoCommon and .NET in general. As such, one might reasonably expect it to also be able to interop with classes one writes in IronPython or C#. Perhaps @eirannejad or @stevebaer have some insights here.
For completeness: judging by the error message, have you tried taking it out of the if __name__ == "__main__": statement (can’t test it as I haven’t got Rhino 8 installed yet)
What @AndersDeleuran proposes may be interesting, would this be a trivial thing to do though? (haven’t got much experience with .NET)
Since saving another regular python file somewhere and load it back inside GHpython component is exactly NOT what I want to do (can get really messy). I guess pickle is a dead end then.
Go for dict, json or other methods. Or don’t try to do this.
This is evil. I know it has been mentioned. Just want to highlight it again.
Binary data (pickle), Files (Json etc), Dictionaries or NET Tuples (anonymous classes) have two common disadvantages. They require you to copy and paste class definitions to all script components and you waste a lot of resources in piping the data back into something usable.
To prevent that, the only solution is a .dll with .NET classes. Basically a plugin (.gha is a renamed .dll).
Don’t forget, even if you can finally use CPython you are interfacing with NET code. Rhinocommon and Grasshopper are written in .NET.
Rhino 8 is using modern NET, which means you can also use the Roslyn compiler to build in-memory dll’s. In Rhino 7 this works differently, because it relies on the older .NET Framework.
And one more note. The pickle module is insecure if you use in a web situation. People could potentially inject code from an insecure Web UI, if you use pickle somewhere in your backend.
In the context of Grasshopper, it is absolutely secure. Because nobody can or is going to inject source code in your Grasshopper script.
Thanks for your suggestion, this is definitely a good way, and also the correct way when the goal is to finally get a component, publish it or share it with others.
I was doing this changing to SDK mode and drawing icon thing in a short period, then I quit. In my case the component is not the final goal, but some temporary tool to achieve a calculation. They are more like project oriented and get abandoned soon.
But yeah, what you said is definitely what we should do when a component with stable functions is to be made ultimately.