ninia / jep

Embed Python in Java
Other
1.3k stars 147 forks source link

How can I access Python object fields and methods? #414

Closed Daniel-Alievsky closed 2 years ago

Daniel-Alievsky commented 2 years ago

Usually Python program works with objects, their properties and methods. Of course, global variables and functions are also possible, but it is not a good style for OOP code.

But I didn't find in your library a standard way to manipulate fields/methods from Java. I didn't found functions, allowing to create a new Python object (for example, an instance of Python class), to read/write fields of the given object, to call some method of an object. Probably it is possible via "exec" method, but it is not obvious for me, how to do this in some cases.

For example, if some Python function returned an object "x" with field "x.a" - x returned as an Object result of your invoke method - how can I read its "a" property?

Moreover, let's declare some simple class in Python:

class Simple:
    def test(self):
        return 123;

How can I create, from java, an instance of this class and, then, to call "test" method? It is already not a simple function, but a method.

It seems it could be a good idea to add such abilities as a standard methods of the Interpreter interface.

bsteffensmeier commented 2 years ago

PyObject is the easiest way to access python object fields from java. PyCallable can be used to call methods:

        try (SharedInterpreter interpreter = new SharedInterpreter()) {
            interpreter.exec("class Simple:\n" +
                             "    def test(self):\n" +
                             "        return 123;");
            PyCallable simple = interpreter.getValue("Simple", PyCallable.class);
            PyObject instance = simple.callAs(PyObject.class);
            Number result = instance.getAttr("test", PyCallable.class).callAs(Number.class);
            System.out.println(result);
        }

You can also use the proxy capabilities of PyObject to get a more friendly java interface for you python objects:

public interface Simple {
    public int test();
}
        try (SharedInterpreter interpreter = new SharedInterpreter()) {
            interpreter.exec("class Simple:\n" +
                             "    def test(self):\n" +
                             "        return 123;");
            PyCallable simple = interpreter.getValue("Simple", PyCallable.class);
            PyObject instance = simple.callAs(PyObject.class);
            Simple instanceJava = instance.proxy(Simple.class);
            int result = instanceJava.test();
            System.out.println(result);
        }
Daniel-Alievsky commented 2 years ago

Thank you, I understand. Looks like I just didn't notice those classes :) Maybe you will add some reference to them into https://github.com/ninia/jep/wiki/Getting-Started ?

Daniel-Alievsky commented 2 years ago

Ok, I understand how to create and access an instance of some class, which is declared in Python code. In your example it is "Simple" class. However, I'd prefer not to create any new entities from Java code without necessity: all users of this interpreter will see new class "Simple". Could you advise, maybe there is a good way to create an "anonymous" object, containing some set of fields, and pass it to some Python function?

I'd like to implement something like this. I have a user's Python code:

def customFunc(params):
    some processing params

It is supposed that params contain some parameters, for example, params.a, params.b, etc., filled from Java side and analysed in Python code. Can I create, in Java, some instance of object "params" without declaring any special Python classes, then add there some fields (numbers, strings, ND-arrays), and then call "customFunc" with this object via "invoke" method?

Sorry for too simple question, Python is a new area for me.

Daniel-Alievsky commented 2 years ago

Also, I see method "getAttr", but don't see method like "hasAttr", allowing the check whether an object contains the attribute. How can I get the list of all attributes in Java?

bsteffensmeier commented 2 years ago

It sounds like your python function should be written to take arbitrary keyword arguments. Both PyCallable.call and Interpreter.invoke are overloaded to take a Map<String,Object> that will be used for the keyword args.

If you cannot use kwargs or Map you could also define a custom java class with fields a, b, etc... and set those fields. When that Java object is passed into a python function the fields can be accessed like ordinary python attributes.

To get a list of attributes for a python object you can use the python builtin dir() function.

Daniel-Alievsky commented 2 years ago

How can I call standard functions like dir, hasattr, str, ... from Java?

I have an instance python object as "PyObject obj". obj.getAttr("__dir__", PyCallable.class).call(); works fine: it returns a list of attributes. interp.invoke("dir", obj) doesn't work at all, an exception: Unable to find object with name: dir

Moreover, I tried to call other standard functions like str(), print(), etc. - "invoke" method does not recognize them. What is the reason?

Daniel-Alievsky commented 2 years ago

It sounds like your python function should be written to take arbitrary keyword arguments. Both PyCallable.call and Interpreter.invoke are overloaded to take a Map<String,Object> that will be used for the keyword args.

No, my desire was little another. I'm working over Java system (named Stare), where the user can write his own Python code (in a little textbox), and I will execute it via JEP. But what is "Python code"? I speak about something useful for commercial project, written according some simple standard. In other words, it should be a function with some simple name, that has some parameters and returns some information. But different users need different sets of parameters, different results. These sets may be configured in different ways; in particular, I offer user to fill some JSON, where all parameters and results are specified in some simple form.

How can it be implemented on Python side? My idea is to offer the user to write a function with maximally simple syntax:

def customFunc(params):
    # we have, for example, params.price, params.amount, params.matrix_to_process (numpy)
    # some processing params
    # create result object - maybe instance of some class
    result.total = something
    result.smoothed_matrix = ...
    return result

Set of attributes in params and result are specified by JSON, provided by user.

How can I create "params" from Java? I know only one way: I should declare, before interpreting this code, some special python class, for example:

class StareParams:
    pass

Then, really, I can create its instance (as you described) and then add any necessary attributes according JSON model, for example, obj.setAttr("price", 314.2);

Sounds good, and probably I will use this way. But here is only one little problem: I need to add special class StareParams, than will be visible inside user's Python code. What will be if he will declare class StareParams himself??

Of course, I'd prefer to use some standard, already existing class, allowing to add any fields. In JavaScript I could use something like "new Object()". Unfortunately, Python analog "object()" does not allow to add fields dynamically, because it has no __dict__ attribute. I can create an instance of standard dict object: interp.getValue("dict", PyCallable.class).callAs(PyObject.class); But it will be no so convenient on Python side...

It seems here is not better solution, than create special class with standard name like StareParams and warn the user that it is reserved standard name.

bsteffensmeier commented 2 years ago

Moreover, I tried to call other standard functions like str(), print(), etc. - "invoke" method does not recognize them. What is the reason?

You will need to import builtins so that the dir method will be available in the globals. You can do from builtins import dir or just import builtins' and then invoke withbuiltins.dirinstead ofdir`

Daniel-Alievsky commented 2 years ago

Thank you for your explanations!