Skip to content

Inconsistent TypeError messages with callables #136327

Closed

Description

@Tapeline

Bug report

Bug description:

Consider this example:

>>> class A:
...     def __str__(self): return "some random text"
...     def __call__(self, a): ...

>>> A()()
Traceback (most recent call last):
  File "<python-input-16>", line 1, in <module>
    A()()
    ~~~^^
TypeError: A.__call__() missing 1 required positional argument: 'a'

>>> A()(*None)
Traceback (most recent call last):
  File "<python-input-17>", line 1, in <module>
    A()(*None)
    ~~~^^^^^^^
TypeError: some random text argument after * must be an iterable, not NoneType

The first one gets the callable's __qualname__. The second, on the other hand, does not, but should do so.

Another example, mentioned in #135975 (issuecomment link):

>>> exit(*None)
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    exit(*None)
    ~~~~^^^^^^^
TypeError: Use exit() or Ctrl-Z plus Return to exit argument after * must be an iterable, not NoneType

Which just gets the exit's __repr__.

If this is recognized as unwanted behaviour, I'm ready to make a PR.

CPython versions tested on:

3.13

Operating systems tested on:

Windows

Edit: ** also does not work properly:

# the code from the first example
>>> A()(**1)
Traceback (most recent call last):
  File "<python-input-26>", line 1, in <module>
    A()(**1)
    ~~~^^^^^
TypeError: some random text argument after ** must be a mapping, not int

Linked PRs

Activity

  1. self-assigned this
    on Jul 6, 2025
  2. added a commit that references this issue on Jul 6, 2025
  3. sobolevn commented on Jul 6, 2025

    @sobolevn
    Member

    If this is recognized as unwanted behaviour, I'm ready to make a PR.

    Please, go ahead :)
    I would love to review it!

  4. Tapeline commented on Jul 7, 2025

    @Tapeline
    ContributorAuthor

    I've done some investigation on this bug. Seems that it's harder, than I initially thought.

    The function's name is received via _PyObject_FunctionStr, which indeed gets the function's qualname. If the qualname isn't present, it just gets whatever is in __str__/__repr__ of the function.

    Regarding the examples I've shown earlier:

    1. Example with exit is behaving this way, because some objects in __builtins__ for some reason do not have the __qualname__ attribute. Here's the full list:
    Ellipsis
    False
    None
    NotImplemented
    True
    _
    __debug__
    __doc__
    __name__
    __package__
    __spec__
    copyright
    credits
    exit
    help
    license
    quit
    

    I'm not quite sure about the dunder ones, but all others definetly should have a qualname.

    1. The example with the class, on the other hand works that way, because the constructed object is passed as a function into _PyObject_FunctionStr. Since it does not have a __qualname__, it defaults to whatever is in __str__/__repr__ of that object. Possible solution would be to just add an edge-case handler to _PyObject_FunctionStr: if an object is not an instance of function or builtin_function_or_method, then we try searching in object.__call__.
  5. added a commit that references this issue on Oct 24, 2025
  6. added a commit that references this issue on Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

interpreter-core(Objects, Python, Grammar, and Parser dirs)type-bugAn unexpected behavior, bug, or error

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions