Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mxgordon opened this issue Mar 4, 2025 · 10 comments
Labels
easy interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement

Comments

@mxgordon
Copy link

mxgordon commented Mar 4, 2025

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

@mxgordon mxgordon added the type-bug An unexpected behavior, bug, or error label Mar 4, 2025
@skirpichev
Copy link
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.

@skirpichev skirpichev added type-feature A feature request or enhancement interpreter-core (Objects, Python, Grammar, and Parser dirs) and removed type-bug An unexpected behavior, bug, or error labels Mar 4, 2025
@serhiy-storchaka serhiy-storchaka added the 3.14 new features, bugs and security fixes label Mar 4, 2025
@ZeroIntensity
Copy link
Member

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

@donBarbos
Copy link
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)

@donBarbos
Copy link
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?

@picnixz picnixz removed the 3.14 new features, bugs and security fixes label Mar 4, 2025
@serhiy-storchaka
Copy link
Member

Looks like a nice idea to me.

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

@donBarbos
Copy link
Contributor

donBarbos commented Mar 6, 2025

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

@serhiy-storchaka
Copy link
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.

@donBarbos
Copy link
Contributor

donBarbos commented Mar 6, 2025

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 :)

@serhiy-storchaka
Copy link
Member

Just change the error messages. Inclusion of the type is a plus, and since we change the error messages, we can use an opportunity to unify them more. Propose several variants, add variants proposed by commenters, and open a poll.

My English is poor, so I can't choose the best variant. But I support the change in general.

@donBarbos
Copy link
Contributor

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
easy interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

6 participants