Skip to content

Inconsistent error messages when returning the wrong type for the type-conversion magic methods #130821

Description

@mxgordon

Bug report

Bug description:

I noticed the error messages between magic methods like __int__ and __float__ were inconsistent. This seems like slightly undesirable behavior to me. I used the following code to generate many of them.

class Foo:
    def __int__(self):
        return None
    
    def __float__(self):
        return None
    
    def __bytes__(self):
        return None
    
    def __complex__(self):
        return None
    
    def __bool__(self):
        return None
    
    def __str__(self):
        return None

try:
    int(Foo())
except Exception as e:
    print(e)

try:
    float(Foo())
except Exception as e:
    print(e)

try:
    bytes(Foo())
except Exception as e:
    print(e)

try:
    complex(Foo())
except Exception as e:
    print(e)

try:
    bool(Foo())
except Exception as e:
    print(e)

try:
    str(Foo())
except Exception as e:
    print(e)

And the output is as follows:

__int__ returned non-int (type NoneType)
Foo.__float__ returned non-float (type NoneType)
__bytes__ returned non-bytes (type NoneType)
__complex__ returned non-complex (type NoneType)
__bool__ should return bool, returned NoneType
__str__ returned non-string (type NoneType)

The first issue I've made, but seems like a reasonable bug. I'm not sure if there are other "type-conversion" magic methods out there that aren't consistent.

CPython versions tested on:

3.13, 3.12

Operating systems tested on:

Windows

Linked PRs

Activity

  1. skirpichev commented on Mar 4, 2025

    @skirpichev
    Member

    The difference is

    cpython/Objects/abstract.c

    Lines 1529 to 1534 in d780f0a

    if (!PyLong_Check(result)) {
    PyErr_Format(PyExc_TypeError,
    "__int__ returned non-int (type %.200s)",
    Py_TYPE(result)->tp_name);
    Py_DECREF(result);
    return NULL;

    vs

    cpython/Objects/abstract.c

    Lines 1607 to 1612 in d780f0a

    if (!PyFloat_Check(res)) {
    PyErr_Format(PyExc_TypeError,
    "%.50s.__float__ returned non-float (type %.50s)",
    Py_TYPE(o)->tp_name, Py_TYPE(res)->tp_name);
    Py_DECREF(res);
    return NULL;

    CC @serhiy-storchaka as author of 16931c3.

    I don't think that inconsistency is a real bug. But that does make sense for me as a feature request: i.e. include type information to the exception message.

  2. added
    type-featureA feature request or enhancement
    interpreter-core(Objects, Python, Grammar, and Parser dirs)
    and removed
    type-bugAn unexpected behavior, bug, or error
    on Mar 4, 2025
  3. ZeroIntensity commented on Mar 4, 2025

    @ZeroIntensity
    Member

    I've marked this as easy for anyone that wants to dive into the C code for the first time.

  4. donbarbos commented on Mar 4, 2025

    @donbarbos
    Contributor

    if no one minds I'm going to send a PR with a unified message using as a reference message: Foo.__float__ returned non-float (type NoneType)

  5. donbarbos commented on Mar 4, 2025

    @donbarbos
    Contributor

    but after "grep" sources i found more cases with "returned non-" template, excluding tests and docs:

    grep output
    
    ./Objects/abstract.c:                     "__index__ returned non-int (type %.200s)",
    ./Objects/abstract.c:            "__index__ returned non-int (type %.200s).  "
    ./Objects/abstract.c:                         "__int__ returned non-int (type %.200s)",
    ./Objects/abstract.c:                "__int__ returned non-int (type %.200s).  "
    ./Objects/abstract.c:                         "%.50s.__float__ returned non-float (type %.50s)",
    ./Objects/abstract.c:                "%.50s.__float__ returned non-float (type %.50s).  "
    ./Objects/abstract.c:                         "iter() returned non-iterator "
    ./Objects/bytesobject.c:                         "__bytes__ returned non-bytes (type %.200s)",
    ./Objects/bytesobject.c:                        "__bytes__ returned non-bytes (type %.200s)",
    ./Objects/complexobject.c:                "__complex__ returned non-complex (type %.200s)",
    ./Objects/complexobject.c:                "__complex__ returned non-complex (type %.200s).  "
    ./Objects/funcobject.c:            PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
    ./Objects/moduleobject.c:                PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
    ./Objects/object.c:                      "__repr__ returned non-string (type %.200s)",
    ./Objects/object.c:                      "__str__ returned non-string (type %.200s)",
    ./Objects/object.c:                         "__bytes__ returned non-bytes (type %.200s)",
    ./Objects/fileobject.c:                   "object.readline() returned non-string");
    ./Objects/floatobject.c:                         "%.50s.__float__ returned non-float (type %.50s)",
    ./Objects/floatobject.c:                "%.50s.__float__ returned non-float (type %.50s).  "
    ./Objects/genobject.c:                             "__await__() returned non-iterator "
    ./Objects/typeobject.c:                PyErr_Format(PyExc_TypeError, "__annotate__ returned non-dict of type '%.100s'",
    ./Objects/typeobject.c:                     "__buffer__ returned non-memoryview object");
    ./Lib/subprocess.py:            return "Command '%s' returned non-zero exit status %d." % (
    ./Modules/_datetimemodule.c:                         "divmod() returned non-tuple (type %.200s)",
    ./Modules/_abc.c:                "items() returned non-iterable");
    ./Modules/_pickle.c:                         "read() returned non-bytes object (%R)",
    

    should we add type information to other methods and to methods from modules in stdlib?

  6. removed
    3.14bugs and security fixes
    on Mar 4, 2025
  7. serhiy-storchaka commented on Mar 4, 2025

    @serhiy-storchaka
    Member

    Looks like a nice idea to me.

    You can use %T and %N to format type names in C.

  8. donbarbos commented on Mar 6, 2025

    @donbarbos
    Contributor

    but after "grep" sources i found more cases with "returned non-" template, excluding tests and docs:
    grep output

    should we add type information to other methods and to methods from modules in stdlib?

    here are more examples of error messages:

    __len__() should return >= 0
    __hash__ method should return an integer
    __init__() should return None, not '%.200s'
    __sizeof__() should return >= 0
    __getnewargs_ex__ should return a tuple, not '%.200s'
    __getnewargs_ex__ should return a tuple of length 2, not %zd
    __length_hint__() should return >= 0
    __length_hint__ must be an integer, not %.100s
    read() should return bytes
    readall() should return bytes
    decoder should return a string result, not '%.200s'
    encoder should return a bytes object, not '%.200s'
    iterator should return strings, not %.200s (the file should be opened in text mode)
    

    I suggest using the following template for all of them: {TYPE}.__method__ should return ..., not ... (or must return)
    unfortunately we don't have a PEP about error messages, but we had a similar PR #127345 and I found discuss

  9. serhiy-storchaka commented on Mar 6, 2025

    @serhiy-storchaka
    Member

    I like it. But since it's such a wide change, we need approval from more core developers. Could you open a discussion at https://discuss.python.org/?

    I think also that there should be parentheses after the method name. This would help to distinguish them from attributes.

  10. donbarbos commented on Mar 6, 2025

    @donbarbos
    Contributor

    Could you open a discussion at https://discuss.python.org/?

    yes, i can. but should i propose in this discussion to create PEP or just propose to change error messages of the methods i have cited?

    EDIT: I don't mind if you open it. Maybe you know better what to use as arguments :)

  11. 9 remaining items

  12. added a commit that references this issue on Aug 19, 2025
  13. added a commit that references this issue on Feb 12, 2026
  14. dioput12 commented on Apr 23, 2026

    @dioput12

    Hello All, I just tried this minimal_repro.py and here's the result on Termux with 3.13.13 version:

    # Run: python3 -c "..."
    
    # __int__ returns the wrong type
    class A:
        def __int__(self): return "not an int"
    
    class B:
        def __float__(self): return 42  # returns an int, not a float
    
    class C:
        def __bool__(self): return 1.0  # returns a float, not a bool
    
    class D:
        def __index__(self): return 3.14
    
    try: int(A())
    except TypeError as e: print(f"__int__:   {e}")
    
    try: float(B())
    except TypeError as e: print(f"__float__: {e}")
    
    try: bool(C())
    except TypeError as e: print(f"__bool__: {e}")
    
    try: D()[0:1]
    except TypeError as e: print(f"__index__: {e}")
    ~ $ python3 minimal_repro.py
    __int__:   __int__ returned non-int (type str)
    __float__: B.__float__ returned non-float (type int)
    __bool__: __bool__ should return bool, returned float
    __index__: 'D' object is not subscriptable
    ~ $
  15. added 3 commits that reference this issue on Jun 22, 2026
  16. added a commit that references this issue on Jul 24, 2026
  17. JMak-Security commented on Aug 20, 2026

    @JMak-Security

    I found that Modules/_abc.c is still using the old "items() returned non-iterable" error format. I am currently working on a patch to update it to the new consistent format with type information!

  18. added 3 commits that reference this issue on Aug 20, 2026
  19. JMak-Security commented on Aug 20, 2026

    @JMak-Security

    Since PR #144737 has gone stale, I would love to take over this issue. I can port over my fixes for _abc.c, tackle the other listed modules, and address the reviewer's feedback regarding whether we should switch to TypeError or keep ValueError in _pickle.c.

  20. added a commit that references this issue on Aug 20, 2026
  21. JMak-Security commented on Aug 20, 2026

    @JMak-Security

    Opened #156120 for the Modules/_abc.c piece — narrower in scope than #144737 (just this one file), and fixes a NULL-dereference risk found in that PR's version of the same hunk (passing NULL as the message to PySequence_Fast(), which can reach PyErr_SetString(exc, NULL) on a genuine TypeError). Left a note on #144737 as well. Not touching the other Modules/ files yet since the _pickle.c ValueError/TypeError question there is still open and this issue is still flagged as needing broader consensus before a repo-wide sweep.

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

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-featureA feature request or enhancement

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions