kivy / pyjnius

Access Java classes from Python
https://pyjnius.readthedocs.org
MIT License
1.4k stars 255 forks source link

Boolean conversions between Java and Python #602

Open lg8080 opened 3 years ago

lg8080 commented 3 years ago

When trying to use booleans with Pyjnius, they get converted to integers in some situations, which causes errors.

>>> import jnius
>>> jnius.__version__
'1.3.0'
>>> Boolean = jnius.autoclass('java.lang.Boolean')
>>> Boolean("true")  # this is consistent with java.lang.Integer behavior
<java.lang.Boolean at 0x11b53d5c8 jclass=java/lang/Boolean jself=<LocalRef obj=0x7fd240748ff0 at 0x10e942ef0>>
>>> Boolean.TRUE  # should return True (to be consistent with java.lang.Integer.MAX_VALUE
1
>>> Boolean("true").compareTo(True)  # should return 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "jnius/jnius_export_class.pxi", line 1145, in jnius.JavaMultipleMethod.__call__
  File "jnius/jnius_export_class.pxi", line 857, in jnius.JavaMethod.__call__
  File "jnius/jnius_export_class.pxi", line 954, in jnius.JavaMethod.call_method
  File "jnius/jnius_jvm_dlopen.pxi", line 91, in jnius.create_jnienv
jnius.JavaException: JVM exception occurred: java.lang.Integer cannot be cast to java.lang.Boolean java.lang.ClassCastException
>>> Stack = jnius.autoclass('java.util.Stack')
>>> s = Stack()
>>> s.isEmpty()
True
>>> s.push(4)
4
>>> s.push(True)  # should return True
1
>>> s.push(Boolean("true"))  # should return True
1

The following test shows a change in behaviour between the current master branch and pyjnius 1.3.0:

  1. Create a Java class with a function that accepts {{Boolean}} as argument:

    class Test {
      public static void func(Boolean x) {
        System.out.println(x);
      }
    }
  2. Call it from Python using pyjnius with a Python boolean:

    python -c "from jnius import autoclass; test = autoclass('Test'); test.func(True)"

This will run on Pyjnius 1.3.0 (and will print out 1 instead of true), but on the current master branch of pyjnius this will fail with TypeError: Invalid instance of 'java/lang/Integer' passed for a 'java/lang/Boolean'

If you use boolean instead of Boolean, it will work correctly on both versions and will print out true.

mmj579 commented 1 year ago

Confirmed: There is no way to specify instances of Booleans in the current release. Booleans get converted to Integer(1). Unfortunately my Java code expects instances of Boolean, not of Integer. This bug ate up 3 hours of my time just to identify it :-( Didn't expect anything like it. And I don't know how I can work around it. The only way seems to be to reimplement my python logic in Java :-(

cmacdonald commented 1 year ago

Cant you pass python True and Java autoboxing will deal with it?

mmj579 commented 1 year ago

Unfortunately: No.

I did something like this:

myList = jnius.autoclass("java.util.ArrayList")
myList.add(jnius.autoclass("java.lang.Boolean").TRUE)

(BTW: jnius.autoclass("java.lang.Boolean").TRUE should return an instance of Boolean.)

The contents of that list seems to be an Integer(1). Somehow for some reason (= details are still unclear to me) instances of Boolean get converted to Integer. My Java code (correctly) complains that it receives instances of Integer instead of Boolean.

I took this quite uncommon approach of using Boolean.TRUE because of passing Python True and relying on autoboxing did lead to the unexpected result of receiving an Integer, not a Boolean.

mmj579 commented 1 year ago

Ah, that's interesting. If my Java code returns Boolean.TRUE as a return value of a method I receive an integer 1 in Python.

mmj579 commented 1 year ago

In Python 1 as equivalent to true. So it's quite natural that most code works quite well though we've a bug here.

Please note:

>>> type(True)
<class 'bool'>
>>> type(1)
<class 'int'>

And:

>>> isinstance(1, bool)
False
>>> isinstance(1, int)
True

But:

>>> isinstance(True, bool)
True
>>> isinstance(True, int)
True
cmacdonald commented 1 year ago

Don’t think about Python - There is custom conversions code - see populate_args() in

https://github.com/kivy/pyjnius/blob/master/jnius/jnius_conversion.pxi [pyjnius.png] pyjnius/jnius/jnius_conversion.pxi at master · kivy/pyjniushttps://github.com/kivy/pyjnius/blob/master/jnius/jnius_conversion.pxi github.comhttps://github.com/kivy/pyjnius/blob/master/jnius/jnius_conversion.pxi

Perhaps we need a bit more handling here for booleans.

Craig

Sent from my iPhone

On 25 Jul 2023, at 21:43, mmj579 @.***> wrote:



In Python 1 as equivalent to true. So it's quite natural that most code works quite well though we've a bug here.

Please note:

type(True) <class 'bool'> type(1) <class 'int'>

And:

isinstance(1, bool) False isinstance(1, int) True

But:

isinstance(True, bool) True isinstance(True, int) True

— Reply to this email directly, view it on GitHubhttps://github.com/kivy/pyjnius/issues/602#issuecomment-1649871353, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAEXTCRQ5KN4C7ISJT3SLSTXR7EKPANCNFSM5B357OTQ. You are receiving this because you commented.Message ID: @.***>

lg8080 commented 1 year ago

@mmj579 As a workaround while this is not fixed yet, you can try this:

myList = jnius.autoclass("java.util.ArrayList")()
myList.add(jnius.autoclass("java.lang.Boolean")("true"))
mmj579 commented 1 year ago

I moved essential logic from Python to Java so I can work around that bug for now. The only remaining problems are values returned as integers instead of booleans. That's not ideal but it's okay. I can live with that.

Thank you for your comprehensive support! In my experience, it's pretty unusual to get feedback this quickly, especially on open source projects. I really appreciate that!

And: Pyjnius is a fantastic Python module! Very good work!