oracle / graalpython

A Python 3 implementation built on GraalVM
Other
1.17k stars 101 forks source link

Overriding constructors for classes inheriting from Java #367

Open msalman-abid opened 8 months ago

msalman-abid commented 8 months ago

As of the latest version (23.1.0), GraalPy does not support overriding constructors for Python classes that inherit from Java.

The example below throws an error when we try to create a custom Exception wrapper in Python:

package my.pack;

import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.Context;

public class Main {

    static Context context = Context.newBuilder().allowAllAccess(true).build();
    static Value clz;

    public static void main(String[] args) {
        context.eval("python", """
            import java
            class ParseException(java.lang.Exception):
                def __init__(self, message):
                    self.__super__(message)
            """);

        clz = context.getBindings("python").getMember("ParseException");
        Value obj = clz.newInstance("Hello from Java");
    }
}

Result

// error
Exception in thread "main" TypeError: ParseException.__init__() missing 1 required positional argument: 'message'
        at org.graalvm.sdk/org.graalvm.polyglot.Value.newInstance(Value.java:933)
        at my.pack.Main.main(Main.java:18)
msimacek commented 8 months ago

Yes, it's a known limitation, because we need to have already created the Java object by the time we call __init__, so we can pass it as self. Java constructors are not like __init__, you can't call them on an already existing object. We should mention it in the documentation. If all you need is to have a python exception subclass, then don't implement __init__ at all. The arguments get passed to the Java constructor implicitly. Example:

import java
class ParseException(java.lang.Exception):
  pass
print(ParseException("my message").getMessage())
msalman-abid commented 8 months ago

I see, thanks for the explanation. Is this a hard limitation, or will this be addressed in the a future release? If it will remain as-is, an update in the docs will be helpful

msimacek commented 8 months ago

Calling the constructor from __init__ is a hard limitation, Java just doesn't allow calling a constructor on an existing instance. However, what we could do is to allow overriding __new__. Something like this:

class MyException(java.lang.Exception):
  def __new__(cls):
    return cls.__constructor__("my message")

This currently doesn't work, I made up the __constructor__ method. But I think we could make it work.

msalman-abid commented 8 months ago

Will this then be addressed in some upcoming release? We can keep this issue open if required for tracking purposes, and not close it even if #368 is merged

msimacek commented 8 months ago

Let's keep it open, I want to do the __new__ overriding I mentioned earlier.