# Beginner friendly error messages for missing self: "TypeError: A.lol() takes 1 positional argument but 2 were given"

**URL:** <https://discuss.python.org/t/beginner-friendly-error-messages-for-missing-self-typeerror-a-lol-takes-1-positional-argument-but-2-were-given/107414>\
**Category:** Ideas\
**Created:** [May 17, 2026, 2:59am UTC](https://discuss.python.org/t/beginner-friendly-error-messages-for-missing-self-typeerror-a-lol-takes-1-positional-argument-but-2-were-given/107414 "2026-05-17T02:59:44Z")\
**Posts on this page:** 1\
**Showing post:** 13

<div class="post-metadata">

**Author:** ![blhsing](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/blhsing/32/25812_2.png) [@blhsing](https://discuss.python.org/u/blhsing)\
**Post date:** [May 17, 2026, 3:45pm UTC](https://discuss.python.org/t/beginner-friendly-error-messages-for-missing-self-typeerror-a-lol-takes-1-positional-argument-but-2-were-given/107414/13 "2026-05-17T15:45:02Z")

</div>

> [@Jelle](#):
>
> We can’t catch it at class definition time because the language does not require that you use the name `self`. However, the error message could be improved; we say “takes 1 positional argument but 2 were given” when it looks like exactly one was passed. If we detect that we’re calling a bound method and there are too few positionals, the error could mention that one of the bound arguments is the self argument.

I agree, though the challenge is how we can detect that we’re calling a bound method when currently the plumbing is just not there–by the time the error is being generated the context of a bound method is already gone.

One fairly reasonable heuristic I can think of to help detect a bound method call is to check if the type of the first argument has an attribute whose name is the same as the name of the code object of the function being called, and whose value _is_ the code object of the function being called. Unless one deliberately tries to meet these conditions, the heuristic should be enough to determine that a helpful hint is warranted when the number of given arguments is one more than the number of declare parameters.

Here’s my attempt at a modification to the existing `too_many_positional` function in `Python/ceval.c` to help illustrate the heuristic:

```c
static int
implicit_self_was_passed(PyCodeObject *co, _PyStackRef *localsplus)
{
    if (co->co_argcount < 1) {
        return 0;
    }
    PyObject *first_name = PyTuple_GET_ITEM(co->co_localsplusnames, 0);
    /* if the first parameter is actually named self, it means the user
       clearly knows about self so there's no need to produce the hint */
    if (_PyUnicode_EqualToASCIIString(first_name, "self")) {
        return 0;
    }
    PyObject *first = PyStackRef_AsPyObjectBorrow(localsplus[0]);
    if (first == NULL) {
        return 0;
    }
    PyObject *attr = _PyType_Lookup(Py_TYPE(first), co->co_name);
    if (attr == NULL) {
        return 0;
    }
    if (!PyFunction_Check(attr)) {
        return 0;
    }
    return ((PyFunctionObject *)attr)->func_code == (PyObject *)co;
}

static void
too_many_positional(PyThreadState *tstate, PyCodeObject *co,
                    Py_ssize_t given, PyObject *defaults,
                    _PyStackRef *localsplus, PyObject *qualname)
{
    /* ... */
    const char *self_hint =
        (given == co_argcount + 1 && implicit_self_was_passed(co, localsplus))
        ? ". Note: 'self' was passed implicitly; did you forget to declare it?"
        : "";
    _PyErr_Format(tstate, PyExc_TypeError,
                  "%U() takes %U positional argument%s but %zd%U %s given%s",
                  qualname,
                  sig,
                  plural ? "s" : "",
                  given,
                  kwonly_sig,
                  given == 1 && !kwonly_given ? "was" : "were",
                  self_hint);
    /* ... */
}

```

---

_[View the full topic](https://discuss.python.org/t/beginner-friendly-error-messages-for-missing-self-typeerror-a-lol-takes-1-positional-argument-but-2-were-given/107414)._
