-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider using JPype #18
Comments
WOW. @Thrameos is a true champion, herding all the cats. 🏆 Thanks very much @imagejan for raising this issue. Especially now, since we will start the push for 1.0.0 very soon. We will need to give JPype a serious look now and see whether it can fully replace our usage of PyJnius. Here is my (incomplete) list of requirements off the top of my head:
And if we're lucky, switching to JPype would magically fix other issues such as imagej/pyimagej#34 too... |
Here is a run down. (Not trying to poach, but just to answer the questions.)
If you just want to make a java object have an altered method use
But this can be painful if there is too many interfaces so the convert style may be better.
This has the advantage that MyJavaInput will match PythonClass everywhere it is used. And because the return type is a duck type there is no need to convert to Python on the return. So if you have a huge interface with many methods taking the same arguments this would be best.
|
@Thrameos Thanks so much for your answer! Regarding Python from Java... it's not super urgent—but will be awesome for our community once it happens. The fact that you are working on it is very much appreciated. About macOS: I'll be testing on macOS for sure, so will let you know what I find out. And if I know how to fix, will file PRs as needed. Hopefully some time in August. There is one other hugely important requirement for us, which I forgot to mention: shared memory. We use pyjnius together with the Unsafe API on the Java side to wrap Python-side memory pointers in Java, so that we can access NumPy images with zero copying. Because our image library (ImgLib2) is interface-driven, all we (@hanslovsky) needed to do was write an alternate off-heap/Unsafe-backed container implementation for ImgLib2, pass the memory pointer to Java from Python, and wrap it. This has been a huge boon for people combining ImageJ with the NumPy world, because bioimage data often reaches many GB in size these days. Do you think this approach will continue to be possible JPype? |
JPype provides direct linkage between shared memory using direct nio buffers that can be created using any unsafe memory. These can be created in Python, Java, or externally. There are some implications about lifespan depending on the source. If it has to survive JVM shutdown to should be Python or externally created. This is all detailed in the userguide. I believe it was part of the 30 pulls in 30 nights campaign thought it may have been earlier. These can be converted to bytes, bytearray, or memoryview on the Python side. They can also be mapped to numpy arrays subject to not shutting down the JVM. So I think we have that need covered. |
Great, thanks again. I'll do my due diligence in the next 1-2 months and explore in more detail! |
Does JPype handle overloading methods when implementing Java interfaces? I know that PuJNIus does that. |
Currently JPype has classes that implement an interface use a dispatch approach rather than directly implementing each overload. I can add individual overloads as a feature request if it is considered important, but no one has ever requested it. Example:
The reason for this is that when calling from Python, we will hit the dispatch as individual overloads are not available. If I had to implement individual overloads I would likely do the following.
Is that what you are looking for? Do you consider this a necessary feature? |
Disclaimer: I am not working on this anymore, so my opinion should be considered food for thought. Ultimately, the active maintainers need to decide what feature would be desirable to them. I defer to @imagejan and @ctrueden to make a decision on this but maybe you find my thoughts helpful: I did not think of the I really do like the way that PyJNIus does it:
In my opinion there are two downsides to this: Programmers need to know how to specify the signature and you have two specify all overloads, even the overloads use the exact same semantics (e.g. passing Summary: JPype overloading
PyJNIus overloading
I personally like the way it is handled in PyJNIus but that may not be generally true for the target audience: Maybe they prefer the more Pythonic way of JPype Another thing to consider: Does JPype auto-convert Java arrays into Python lists? PyJNIus does that and I am not a huge fan of that (I would prefer a JArray object or similar). It is not uncommon, in Java, to pass an array as output parameter. This is impossible to implement on the Python side if the array is copy-converted into a Python list. |
ProxiesYeah that pretty much captures it. Though I can likely support both styles if that is necessary. If you don't put any arguments to User writes
The class factory writes
The prestep would add all the bindings to the class with the signature name (adding the return type). The Python call would then be allowed to delegate through Java if there are overloads specified or directly depending on the implementation. As far as the implementation. Java just passes the name and the method signature name to a Python function which is responsible for deciding which to call. I can change it very easily. The proxy compiler is actually doing method stealing. It gets each class and then traverses methods copying methods and member to a fresh class adding the required mark up for Java. ArraysJPype has a very different concept of arrays. JPype only alters the return type for methods in 3 special cases. null=>None, true=>True, false=>False. This is a requirement because those objects are singletons and can not be overloaded. In all other cases, JPype will return a wrapper object which is duck typing to the nearest Python type. JChar appears to be a length one string with math operations, JInt (and related) are a class derived from int, JFloat( and related) produce a class related to float, and Arrays become a Sequence (called JArray) implementing class which is backed by a Java array. That means you never pay the cost of converting unless explicitly requested. In some cases it is actually more work to send the wrapper, but we do that so that when you chain Java calls, the return type is tracked so it picks the correct overload for the next call. (Much of this was inherited before I picked it up, but that was intended as a system to resolve overloads, and I extended it to cover the return types.) We had the same issue as pyjnius because the original author had made strings convert and that lead to make it impossible to chain certain string operations. I deprecated it and transitioned the project to use wrapper only style over the last year which is why we are finally at 1.0. As mentioned before my local user group that I am supporting are a bunch of physicists so programming is not their specialty. Minimizing the number of rules and making everything duck type so they are not aware which is in Python or in Java was a goal. It does mean the occasional rough spot where you have to insert We do auto convert the other way. dict => HashMap, list=>ArrayList, path=>File, path=>Path, datetime=>Instant, etc. You can abuse it to make some pretty slow running code, but for setting fields once or simple calls these help a lot to make the code appear clean. |
This sounds pretty good and I think it's definitely worth a closer look before the scyjava/imglyb/pyimagej 1.0 release. Specific overloads sound good but it's probably best to evaluate if it's worth the complexity. I like it but I will likely not use in the foreseeable future. |
Specific overloads is about 50 lines of code changed.
This stuff is on the long term task list mostly because much of it is on the list for providing extension of Java classes from Python. But that item has three or four other items in the form of prerequisites in front of it. So if you don't plan to use it, I will likely postpone until the other item comes up (at which time the effort cost will be reduced as all related prerequisites will have been completed.) |
Just wanna make sure that you don't make a decision based on my comment that I won't be using it: I won't be using it because I am not working in the scyjava realm anymore. @ctrueden and @imagejan might have other plans |
Don't worry. I put it on the road map for JPype 2.0. I can always push it forward if someone feels strongly that it is beneficial, or I can keep it there until it is trivial to implement in the future. Usually I will get it inside of a year, unless it is something that requires a huge effort. About the only item that I missed in the last big push was PyPy support and that is only because I was unable to locate someone who was able to help me with debugging. |
Ok, I wonder what would be the changes needed here to switch from
|
The nearest replacement for autoclass is
Options to the JVM are passed to jpype via startJVM. There is a minor difference in philosophy. In PyJnius, configuration happens before PyJnius meaning the JVM must be started, while in JPype the import does not imply the JVM is started until requested. Because of this different, JPype code can have things such as decorating the customizers happen before the JVM. In PyJnius we load the config, set up the options, then import to start
would be this in JPype where we import, then call startJVM to launch.
For the most part JPype provides similar API to pyjnius with some differences to note.
|
There is one other somewhat significant philosophical difference in how JPype functions from other bridges. That is the differences between light and heavy wrappers. Many projects really don't expose the use of Java at all. They are Python libraries that just happen to use Java on the backend to do work. Those types of libraries hide the Java from view. This "heavy" wrappers require coding all the API functionality that will be exposed in Python and maintaining it and the corresponding documentation. You can always use JPype as a backend just like the other bridges. My goal in JPype is to expose Java packages as Python packages entirely. This means that a Java package should "be the Python module". If you say want to add Python functions the Java package "org.scijava" you will simply place a
The intent being to support "light" modules in which the cost of maintaining the Python module is minimal as Java interface (and documentation) serves 90% of the API needs and Python is just needed to add that extra glue like this extra functionality to PyJnius does this under the hood to deal with collections types, but they use hard coded maps so the option of exposing a Java library with full customization isn't really achivable. JPype is more intended to compete in the space of Jython where try mixing of Python and Java code is needed. I am working on giving some finer grain controls to the method dispatch system as well, so that you can just declare the adapters and converters that apply to a class or method rather than needing to define them globally. But that is still being fleshed out. Edit: Not that I am trying to rule out the use of JPype as a backend for a Python library as sometimes people really do want to exposed a different API in Python; it is just the JPype goal is to eliminate having to write a whole additional Python API a requirement to use a Java library. The extra time saved can be used to improve the Java library which benefits everyone. |
Thanks for sharing in so much detail @Thrameos I think that these are really strong points in favor of JPype Starting the JVM on |
Hi everyone -- here's a quick summary of the work done so far to migrate pyimagej from pyjnius to jpype. scyjavaJPype migration status: Complete Working status: Working All tests run successfully with JPype. Structural changes to scyjavaBecause JPype initializes the JVM like this: import jpype
import jpype.imports
jpype.startJVM() And not through an import like pyjnius (e.g. The JPype version of scyjava is on the imglybJPype migration status: Complete Working status: Working Migrating imglyb was straight forward (mostly). I did not make any significant structural changes. The JPype version of imglyb is on a repo attached to my account here (I did not have rights to create a branch on the imglyb repo proper). pyimagejJPype migration status: In progress There are now a two differences between the pyjnius and jpype version of pyimagej. The imagej attribute So far I've replaced the old pyjnius code with JPype and I'm debugging a few issues. Right now I just hit this particular issue with ambigous overloads (there are probably but I haven't found them yet). Ambiguous overloads (example from the pyimagej jupyter notebook tutorial): If you try to run a macro like this: macro = """
#@ String name
#@ int age
#@ String city
#@output Object greeting
greeting = "Hello " + name + ". You are " + age + " years old, and live in " + city + "."
"""
args = {
'name': 'Chuckles',
'age': 13,
'city': 'Nowhere'
}
result = ij._py.run_macro(macro, args) I recieve an Ambiguous overloads error: Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/edward/Documents/repos/loci/pyimagej/imagej/imagej.py", line 319, in run_macro
raise exc
File "/home/edward/Documents/repos/loci/pyimagej/imagej/imagej.py", line 316, in run_macro
return self._ij.script().run("macro.ijm", macro, True, to_java(args)).get()
TypeError: Ambiguous overloads found for org.scijava.script.DefaultScriptService.run(str,str,bool,java.util.LinkedHashMap) between:
public default java.util.concurrent.Future org.scijava.script.ScriptService.run(java.lang.String,java.lang.String,boolean,java.util.Map)
public default java.util.concurrent.Future org.scijava.script.ScriptService.run(java.lang.String,java.lang.String,boolean,java.lang.Object[]) I've also uploaded the The JPype version of pyimagej is on the |
Is there a restriction in JPype that is forcing the leading underscore for |
The
We'd prefer not to use a leading underscore, since those attributes are intended to be part of the public API. But we didn't see an obvious way of doing that. @elevans wrote:
@Thrameos Is there a way to tell JPype which method we want? Our issue here is that we have a |
There are two options that you can use to deal with setting fields that are not allowed. First when you create a class customizer you are allowed to set any field including taking over Java methods and redirecting them specific implementations. Using So lets do an example... here I am going to add an attribute called
This incidentally how you fix problems like LinkedHashMap overload. You override the troublesome method which will rename the original method to have a leading underbar and then implement a new python method use interfaces to decide which is the best overload. Then you use the casting operator to select the specific overload. That said I will look into the the overloading. It would seem like the map would have precedence, but I suspect it is other one of these cases where both ended up with the implicit level. I really need a derived level which matches better, but that is more work and potentially breaks some compatiblity. The other option is to directly set an attribute telling it to bypass the setter. There were changes in Python 3.8 and later that make this option more difficult to do so I am not sure if this will be a good long term option. We used to use this sort of monkey patch often.
Hope that helps. |
I should also note that I have in the past been working on improved customization of methods. For example, JPype used to support a global switch on whether methods should return Python strings or Java strings. Such a global switch makes it hard to use specific Java methods which require chaining, but that doesn't mean that for a specific class it wouldn't make sense to covert strings for methods. To give finer grain control I proposed adding annotations to the customizer class which allow a class, field, or method to specify a type to be used for returns in the form of a map of converters. For example, if the class converters were given as Java String=>str(x) then every method for that class would produce a string automatically (without breaking the basic Java classes). We could add "rename" to a specific overload or other customization options so long as they are easy to use as simple markups using decorators. This is towards my long term goal that Java classes should be easily customized to fit the requirements of Python so people don't need to write heavy wrappers. |
I am in the process of releasing JPype 1.1 and starting the next cycle. If you have any requests for the next so that I can address in the next release now would be a good time. I assume that the ImplementationFor pattern solved the last issue. |
@Thrameos Thanks, with your example I was able to get the |
I tried to work up an example of the ambiguity that you were seeing but I was unable to formulate anything in which a linked hash map would have problems with Object[]. It would seem more likely that it was a static vs member overload problem. Example:
And
Gives "1,2,2" as expected without exceptions for JPype 1.1. So the first question is can I see the definitions of the method with an issue, the line that caused the issue, and the type of each of the arguments? If for some reason I forget the object instance then I would get something like...
It seems it is having problems with the first argument (Object[] or Map vs LinkedHashMap) when it is really having issues with argument 0. We gave it a Test class which does not fit with a Test instance. This was a common point of confusion so we modified it to make it more likely to clue the user in. The new and improved error message reads...
If this isn't the problem I would need a short example so I can look over the method resolution procedure. |
@Thrameos Thanks for looking into it! Did you try with varargs |
I did not explicitly test “Object…” perhaps the variable argument it the problem here. I may be able to make a rule that variable arguments should prefer a single match if that is the problem without breaking anything else.
|
I ran some tests and it does appear that the varargs is part of the problem. The name resolution which constructs that method resolution table in Java does not appear place a resolution for Map over Object because it considers Object[] and Map to be separate non-derived types without considering a single Object instance. It appears to be a bug in org.jpype.manager.MethodResolution. It needs an additional clause in |
For those that are interested here is how to resolve this kind of issue. We have two methods which appear to be ambiguous but one has more specific types. In order to know which one to use there is a table which consults a cache of of method takes precedence over method. So we have call(Object...) vs call(Map). It should say call(Map) is more specific than call(Object). The table is built when the dispatch is created (we don't currently have a way to alter it). Each method in the dispatch is compared against each other method in a pair wise fashion calling org.jpype.manager.MethodResolution.isMoreSpecificThan. Fortunately these methods are all exposed to the user so we can debug it directly by manually calling the internal methods of jpype.
We get the result 'False,False' so it currently has no preference between them hence a warning issued if both resolutions match. To fix that we just need to check if one of the methods is varargs and add some extra code so that it recognizes The finished product is in jpype-project/jpype#863 |
@Thrameos That's awesome, thank you so much for explaining how it works! If we encounter more ambiguities like this, we'll dig in and file a PR (or at least have more specific details if we get stuck). 🥇 |
@Thrameos I think your fix was correct, but actually didn't work for our specific case due to I opened a PR to address this... hopefully |
Nope. Likely an oversight. I merged it in. Likely there needs to be more tests in the test bench so that we can make sure things don't break. However, that should be part of a larger effort to test the MethodResolution class. |
Just a quick update for lurkers: the following branches go together now:
Things are largely working thanks to @elevans's efforts. We'll keep pushing on it, with the goal of getting 1.0.0 releases of the component stack (jgo, scyjava, imglyb, pyimagej) done by mid-November. |
JPype was just released as version 1.0.0
Maybe this could be an alternative to PyJnius? What would be the respective benefits/drawbacks?
See also the discussion at kivy/pyjnius#551.
The text was updated successfully, but these errors were encountered: