Skip to content

float() rejects instances of an int subclass #19874

Open

Description

@jseop-lim

Port, board and/or hardware

Unix port, macOS arm64

MicroPython version

MicroPython v1.30.0-preview-247-gcbbdb6379

Reproduction

class MyInt(int):
    pass

print(float(MyInt(2)))

Expected behaviour

CPython prints (3.11.15):

2.0

Observed behaviour

TypeError: can't convert MyInt to float

The same TypeError comes from complex(MyInt(2)), math.sqrt(MyInt(4)), "%f" % MyInt(2) and 1.5 + MyInt(2), while MyInt(2) * 1.5 returns 3.0.

Additional Information

tests/cpydiff/types_int_subclassconv.py documents "No int conversion for int-derived types available" with an int + A example. It does not mention float(), complex() or math, which reach a different function.

Root Cause

mp_obj_get_float_maybe() recognises an integer only as a small int or an object of exactly type int, so a subclass instance falls to MP_UNARY_OP_FLOAT_MAYBE, which instance_unary_op() forwards to the native int, and int never answers it with a float. CPython's PyNumber_Float() calls the nb_float slot that every int subclass inherits.

Fix Suggestion

When MP_UNARY_OP_FLOAT_MAYBE gives no float, convert mp_obj_cast_to_native_base(arg, &mp_type_int) instead, which keeps a subclass's own __float__. That adds 69 bytes on x64 and 44 bytes on arm64 to the unix build-standard firmware (sum of object-file .text, gcc).

Patch, built and tested on master
diff --git a/py/obj.c b/py/obj.c
index 252e7372f..603dbdef3 100644
--- a/py/obj.c
+++ b/py/obj.c
@@ -392,9 +392,12 @@ bool mp_obj_get_float_maybe(mp_obj_t arg, mp_float_t *value) {
     } else if (mp_obj_is_float(arg)) {
         val = mp_obj_float_get(arg);
     } else {
-        arg = mp_unary_op(MP_UNARY_OP_FLOAT_MAYBE, (mp_obj_t)arg);
-        if (arg != MP_OBJ_NULL && mp_obj_is_float(arg)) {
-            val = mp_obj_float_get(arg);
+        mp_obj_t res = mp_unary_op(MP_UNARY_OP_FLOAT_MAYBE, arg);
+        if (res != MP_OBJ_NULL && mp_obj_is_float(res)) {
+            val = mp_obj_float_get(res);
+        } else if ((res = mp_obj_cast_to_native_base(arg, MP_OBJ_FROM_PTR(&mp_type_int))) != MP_OBJ_NULL) {
+            // an int subclass without __float__ converts as its int value
+            return mp_obj_get_float_maybe(res, value);
         } else {
             return false;
         }

I can open this as a PR if the size is acceptable.

Code of Conduct

Yes, I agree

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions