Skip to content

repr() does not preserve str subclass type returned by __repr__ #18942

Closed

Description

@jseop-lim

Port, board and/or hardware

Unix port, Linux

MicroPython version

MicroPython v1.27.0 on 2026-03-12

Reproduction

class MyStr(str):
    pass

class Foo:
    def __repr__(self):
        return MyStr("hello")

result = repr(Foo())
print(type(result))

Expected behaviour

CPython 3.11.15:

<class '__main__.MyStr'>

Observed behaviour

MicroPython v1.27.0:

<class 'str'>

Additional Information

When __repr__ returns an instance of a str subclass, repr() discards the subclass type and returns a plain str. CPython preserves the original subclass type. This is a CPython compatibility issue.

Root Cause

mp_builtin_repr() serializes the __repr__ result into a text buffer (vstr) via mp_obj_print_helper(), then constructs a new plain str from that buffer using mp_obj_new_str_from_utf8_vstr(), which hardcodes &mp_type_str as the type. This discards the original str subclass type.

CPython reference: object.c#L402-L451

// CPython returns the __repr__ result directly, preserving str subclass type
res = (*Py_TYPE(v)->tp_repr)(v);
if (!PyUnicode_Check(res)) {   // PyUnicode_Check accepts str subclasses
    // raise TypeError...
}
return res;                    // original object returned as-is

Fix

Document this as a known difference in tests/cpydiff/ (similar to the existing int subclass entry "No int conversion for int-derived types available"), or change mp_builtin_repr() to return the __repr__ result object directly when it is a str (or subclass) instead of serializing through a buffer.

Code of Conduct

Yes, I agree

Activity

  1. dpgeorge commented on Mar 18, 2026

    @dpgeorge
    Member

    Thanks for the report.

    In my opinion this is a minor discrepancy with CPython and should only be fixed if it can be done at zero cost (no code size change). Fixing it would require a bit of a refactor of how repr and str work.

  2. jseop-lim commented on Mar 20, 2026

    @jseop-lim
    ContributorAuthor

    Agreed, @dpgeorge. Fixing this properly would require a non-trivial refactor, so I understand it can't be addressed right away.

    I've added a cpydiff entry for this difference in PR #18955. Since str() is a core builtin and overriding __str__ is common in practice, I used str() as the example rather than repr(). It also covers a related case where the same root cause causes a discrepancy:

    class MyStr(str):
        pass
    
    class Foo:
        def __str__(self):
            return MyStr("abc")
    
    result = str(Foo())
    print(type(result))
    • MicroPython: <class 'str'>
    • CPython: <class '__main__.MyStr'>

    Would appreciate a review.

  3. 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