`bin()`, `hex()` and `oct()` ignore `__index__`
Port, board and/or hardware
Unix port, macOS arm64
MicroPython version
MicroPython v1.30.0-preview.66.gcc12057519
Reproduction
class A:
def __index__(self):
return 2
print(bin(A()))
Expected behaviour
CPython prints (3.11.15):
0b10
Observed behaviour
MicroPython raises:
ValueError: unknown format code 'b' for object of type 'A'
hex(A()) and oct(A()) raise TypeError: can't convert A to int, while an object defining only __int__ is accepted by hex() and oct() where CPython raises TypeError.
Additional Information
The data model names __index__ as the method bin(), hex() and oct() use (docs). MicroPython's own difference tables list the related 3.8 and 3.10 changes without a status:
Root Cause
mp_builtin_bin (py/modbuiltins.c#L122-L125) passes its argument unconverted to '{:#b}'.format(), so the object lands in the formatter's default: arm and raises ValueError (py/objstr.c#L1607-L1613). The instance coercion used by hex() and oct() goes through MP_UNARY_OP_INT_MAYBE, which looks up only __int__ (py/objtype.c#L387), and __index__ is not looked up anywhere. CPython's bin() calls PyNumber_ToBase, which converts through _PyNumber_Index and calls nb_index.
Fix Suggestion
Map a new MP_UNARY_OP_INDEX_MAYBE to __index__, try it before __int__ in mp_obj_get_int_maybe() (py/obj.c#L358-L373), and coerce the argument in mp_builtin_bin() before formatting. The extra qstr and unary op could sit behind a config option.
Code of Conduct
Yes, I agree
Source: micropython/micropython