Skip to content

repr() does not raise TypeError when __repr__ returns a non-string #18941

Description

@jseop-lim

Port, board and/or hardware

Unix port, Linux

MicroPython version

v1.27.0

Reproduction

class Foo:
    def __repr__(self):
        return True

print(repr(Foo()))

Expected behaviour

CPython v3.11.15:

TypeError: __repr__ returned non-string (type bool)

Observed behaviour

MicroPython v1.27.0:

True

Additional Information

repr() does not raise TypeError when a user-defined __repr__ method returns a non-string value. Instead, MicroPython silently converts and returns the value as a string. This is a CPython compatibility issue.

Root Cause

instance_print in objtype.c calls the user-defined __repr__ method via mp_call_function_1 and passes the result directly to mp_obj_print_helper without validating that the return value is a string.

CPython reference: Objects/object.c#L439-L445

// CPython validates that __repr__ returns a string, raises TypeError otherwise
if (!PyUnicode_Check(res)) {
    _PyErr_Format(tstate, PyExc_TypeError,
                  "__repr__ returned non-string (type %.200s)",
                  Py_TYPE(res)->tp_name);
    Py_DECREF(res);
    return NULL;
}

Fix

Add a mp_obj_is_str(r) check after calling the __repr__ method in instance_print, and raise TypeError if the return value is not a string.

Code of Conduct

Yes, I agree

Activity

  1. Josverl commented on Mar 17, 2026

    @Josverl
    SponsorContributor

    Thanks for sharing.
    Do you have a real world example where this difference is problematic, or is your concern the different behavior compared to Cpython?

  2. jseop-lim commented on Mar 17, 2026

    @jseop-lim
    ContributorAuthor

    @Josverl Thanks for your question.

    My concern is the behavioral difference compared to CPython, and whether this difference is intentional.
    This was discovered via fuzzing rather than a real-world case.

    I’m interested in MicroPython–CPython compatibility and opened this issue to help improve it.

  3. Josverl commented on Mar 17, 2026

    @Josverl
    SponsorContributor

    Thanks for the background info.
    MicroPython's goal is not to be identical to COython, but to balance Micro with Python
    (Also see MicroPython design values in the repro's readme.md)
    So yes, there is a desire to match cpython where possible, but it also should provide practical value.

    However such differences to Cpython should be documented, and there are actually a seperate set of cpydiff test to do just that.

    So there is a choice between implementation, and the assumed associated increase in firmware size, and documenting the differences.

  4. dpgeorge commented on Mar 18, 2026

    @dpgeorge
    Member

    So yes, there is a desire to match cpython where possible, but it also should provide practical value.

    Yes, that's correct.

    In this case I don't think this difference matters in real applications. I doubt there's any code relying on repr() raising an exception for non-string return values.

    So unless this can be fixed at zero-cost (no change in code size) then I think it should be left as-is.

    (I don't even thing it really needs documenting as a difference because it's such a minor thing and we don't want to clutter the list of differences with arguably minor things like this.)

  5. jseop-lim commented on Mar 20, 2026

    @jseop-lim
    ContributorAuthor

    I agree with MicroPython's design values — matching CPython blindly isn't the goal, and I agree with @Josverl and @dpgeorge that keeping the repr() implementation as-is is reasonable given it's unlikely to matter in practice.

    However, the same root cause also affects str():

    class Foo:
        def __str__(self):
            return True
    
    print(str(Foo()))  # MicroPython: True / CPython: TypeError

    This feels more significant — overriding __str__ is something Python developers do regularly, and str() is a core builtin. I've opened PR #18955 to document this difference. Would appreciate a review.

  6. dpgeorge commented on May 14, 2026

    @dpgeorge
    Member

    Documented in 3ef656a

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions