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):
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:
|
| Constructors of *int*, *float* and *complex* will now use the *__index__()* special method, if available | | |
|
| and the corresponding method *__int__()*, *__float__()* or *__complex__()* is not available | | |
|
| Builtin and extension functions that take integer arguments no longer accept | | |
|
| :class:`~decimal.Decimal`\ s, :class:`~fractions.Fraction`\ s and other | | |
|
| objects that can be converted to integers only with a loss (e.g. that have | | |
|
| the :meth:`~object.__int__` method but do not have the | | |
|
| :meth:`~object.__index__` method). | | |
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
Port, board and/or hardware
Unix port, macOS arm64
MicroPython version
MicroPython v1.30.0-preview.66.gcc12057519
Reproduction
Expected behaviour
CPython prints (3.11.15):
Observed behaviour
MicroPython raises:
hex(A())andoct(A())raiseTypeError: can't convert A to int, while an object defining only__int__is accepted byhex()andoct()where CPython raisesTypeError.Additional Information
The data model names
__index__as the methodbin(),hex()andoct()use (docs). MicroPython's own difference tables list the related 3.8 and 3.10 changes without a status:micropython/docs/differences/python_38.rst
Lines 46 to 47 in b0310f5
micropython/docs/differences/python_310.rst
Lines 76 to 80 in b0310f5
Root Cause
mp_builtin_bin(py/modbuiltins.c#L122-L125) passes its argument unconverted to'{:#b}'.format(), so the object lands in the formatter'sdefault:arm and raisesValueError(py/objstr.c#L1607-L1613). The instance coercion used byhex()andoct()goes throughMP_UNARY_OP_INT_MAYBE, which looks up only__int__(py/objtype.c#L387), and__index__is not looked up anywhere. CPython'sbin()callsPyNumber_ToBase, which converts through_PyNumber_Indexand callsnb_index.Fix Suggestion
Map a new
MP_UNARY_OP_INDEX_MAYBEto__index__, try it before__int__inmp_obj_get_int_maybe()(py/obj.c#L358-L373), and coerce the argument inmp_builtin_bin()before formatting. The extra qstr and unary op could sit behind a config option.Code of Conduct
Yes, I agree